设相对布局,则x,y更改无效 horizontalCenter="0" verticalCenter="120"

本文介绍了解决在相对布局中使用horizontalCenter与verticalCenter属性导致x,y坐标失效的问题,通过设置样式为null来恢复正常定位。
//CurvePlot.qml import QtQuick 2.15 import QtQuick.Controls 2.15 Item { id: root width: 776 height: 476 property var channelDataList: [] property var channelsMaxMins: [] property real chartLeftMargin: 60 property real chartRightMargin: 20 property real chartTopMargin: 40 property real chartBottomMargin: 40 property real chartWidth: Math.max(1, width - chartLeftMargin - chartRightMargin) property real chartHeight: Math.max(1, height - chartTopMargin - chartBottomMargin) property bool isCanvasReady: false // Canvas是否完成布局(尺寸稳定) property bool hasValidData: false // 数据是否有效(通道数据+最大最小值均有效) property bool shouldDraw: false // 是否需要触发绘制 Component.onCompleted: { console.log("CurvePlot初始化完成,初始尺寸:", width, "x", height) isCanvasReady = true Qt.callLater(tryTriggerDraw) } function calculateNormalizedValue(value, minValue, maxValue) { if (maxValue === minValue) return 0.5 return (value - minValue) / (maxValue - minValue) } function tryTriggerDraw() { if (Date.now() - (lastTryTime || 0) < 100) return lastTryTime = Date.now() console.log(`尝试触发绘制 | 尺寸有效:${isCanvasReady} 数据有效:${hasValidData} 当前尺寸:${width}x${height}`) if (isCanvasReady && hasValidData && width > 0 && height > 0) { console.log("满足绘制条件,请求重绘!") shouldDraw = true canvas.requestPaint() } else { console.log("暂不满足绘制条件,等待布局或数据更新...") if (isCanvasReady && !hasValidData) { retryCount = (retryCount || 0) + 1 if (retryCount < 3) { Qt.callLater(tryTriggerDraw) } else { console.log("数据验证失败超过3次,停止重试") } } } } function getSeriesColor(index) { const colors = [ "#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#FFA500", "#800080", "#008000", "#000080", "#808000", "#800000", "#008080", "#C0C0C0", "#808080", "#FFFFFF" ] return colors[index % colors.length] } onChannelDataListChanged: { console.log("检测到channelDataList变化,重新验证数据...") validateData() tryTriggerDraw() } onWidthChanged: { console.log("CurvePlot宽度变化:", width) debounceSizeChange() } onHeightChanged: { console.log("CurvePlot高度变化:", height) debounceSizeChange() } property int sizeChangeDebounce: 0 function debounceSizeChange() { sizeChangeDebounce++ if (sizeChangeDebounce === 1) { Qt.callLater(() => { isCanvasReady = true tryTriggerDraw() sizeChangeDebounce = 0 }, 100) } } function validateData() { hasValidData = false retryCount = 0 if (channelDataList.length === 0) { console.log("数据无效:channelDataList为空") return } if (channelsMaxMins.length === 0) { console.log("数据无效:channelsMaxMins为空") return } if (channelDataList.length !== channelsMaxMins.length) { console.log(`数据无效:通道数不匹配(channelDataList=${channelDataList.length}, channelsMaxMins=${channelsMaxMins.length})`) return } for (let i = 0; i < channelsMaxMins.length; i++) { const range = channelsMaxMins[i] if (!range || typeof range.minValue !== "number" || typeof range.maxValue !== "number") { console.log(`数据无效:通道${i}的最大最小值无效(min=${range?.minValue}, max=${range?.maxValue})`) return } } for (let j = 0; j < channelDataList.length; j++) { const channel = channelDataList[j] if (!channel || !channel.points || channel.points.length === 0) { console.log(`数据无效:通道${i}无数据点(points=${channel?.points?.length})`) return } for (let k = 0; k < channel.points.length; k++) { const point = channel.points[k] if (typeof point.value !== "number") { console.log(`数据无效:通道${j}的点${k}值无效(value=${point.value})`) return } } } hasValidData = true console.log(`数据验证通过!通道数:${channelDataList.length},每个通道数据有效。`) } property int retryCount: 0 property int lastTryTime: 0 Rectangle { id: chartBackground anchors.fill: parent color: "#212529" Canvas { id: canvas anchors.fill: parent Component.onCompleted: { console.log("Canvas布局完成,父容器实际尺寸:", parent.width, "x", parent.height) Qt.callLater(tryTriggerDraw) } onPaint: { if (!shouldDraw) { console.log("跳过绘制(条件不满足)") return } console.log(`开始绘制 | 实际尺寸:${width}x${height},数据通道数:${channelDataList.length}`) const ctx = getContext("2d") ctx.reset() ctx.clearRect(0, 0, width, height) // 清空画布 ctx.fillStyle = "#212529" ctx.fillRect(0, 0, width, height) ctx.fillStyle = "#2E2E2E" ctx.fillRect(chartLeftMargin, chartTopMargin, chartWidth, chartHeight) drawGridLines(ctx) drawAxes(ctx) if (hasValidData) { drawCurves(ctx) } else { ctx.fillStyle = "white" ctx.font = "16px Arial" ctx.textAlign = "center" ctx.fillText("暂无有效数据", width/2, height/2) } } function drawGridLines(ctx) { ctx.strokeStyle = Qt.rgba(1, 1, 1, 0.1) ctx.lineWidth = 1 if (chartHeight > 0 && horizontalGridLines > 1) { const rowHeight = chartHeight / (horizontalGridLines - 1) for (let i = 0; i < horizontalGridLines; i++) { const y = chartTopMargin + i * rowHeight ctx.beginPath() ctx.moveTo(chartLeftMargin, y) ctx.lineTo(width - chartRightMargin, y) ctx.stroke() if (channelsMaxMins.length > 0 && channelsMaxMins[0]) { const range = channelsMaxMins[0] const value = range.maxValue - (i * (range.maxValue - range.minValue) / (horizontalGridLines - 1)) const text = value.toFixed(2) ctx.fillStyle = "white" ctx.font = "12px Arial" ctx.textAlign = "right" ctx.fillText(text, chartLeftMargin - 10, y + 5) } } } } function drawAxes(ctx) { ctx.strokeStyle = "white" ctx.lineWidth = 2 ctx.beginPath() ctx.moveTo(chartLeftMargin, height - chartBottomMargin) ctx.lineTo(width - chartRightMargin, height - chartBottomMargin) ctx.stroke() ctx.beginPath() ctx.moveTo(chartLeftMargin, chartTopMargin) ctx.lineTo(chartLeftMargin, height - chartBottomMargin) ctx.stroke() } function drawCurves(ctx) { console.log("开始绘制曲线,通道数:", channelDataList.length) for (let c = 0; c < channelDataList.length; c++) { const channel = channelDataList[c] const range = channelsMaxMins[c] if (channel.points && channel.points.length > 0 && range) { console.log(`绘制通道 ${c} | 点数:${channel.points.length}`) ctx.strokeStyle = getSeriesColor(c) ctx.lineWidth = 2 ctx.beginPath() const stepX = chartWidth / (channel.points.length - 1) // X轴步长 for (let p = 0; p < channel.points.length; p++) { const point = channel.points[p] if (typeof point.value === "number") { const normalizedY = calculateNormalizedValue( point.value, range.minValue, range.maxValue ) const x = chartLeftMargin + p * stepX // X坐标(自动适配边距) const y = chartTopMargin + (1 - normalizedY) * chartHeight // Y坐标(自动适配边距) if (p === 0) { ctx.moveTo(x, y) } else { ctx.lineTo(x, y) } } } ctx.stroke() ctx.fillStyle = getSeriesColor(c) ctx.font = "12px Arial" ctx.textAlign = "left" ctx.fillText( channel.channelName || `通道 ${c+1}`, width - 150, 30 + c * 20 ) } } } } } } //DataLogShow.qml import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Shapes 2.15 import QtQuick.Layouts 1.15 import CsvLogger 1.0 Item { // 基准分辨率(计稿尺寸) property real baseWidth: 1024 property real baseHeight: 600 // 动态计算缩放比例 property real scaleX: width / baseWidth property real scaleY: height / baseHeight property real unifiedScale: Math.max(Math.min(scaleX, scaleY),1) property string mountPath: "" property string curFileName: "" // 文件列表模型 property ListModel fileListModel: ListModel {} CsvLogger { id: csvLogger } CustomVirKeyboard { id: keyboard x: (parent.width - width) / 2 y: Math.max(0, parent.height - height - 20) onAccepted: (text) => { if (target) target.text = text keyVisible = false } } MouseArea { anchors.fill: parent propagateComposedEvents: true onClicked: function(mouse) { // 检查点击是否在键盘区域内 var keyboardPos = mapToItem(keyboard, mouse.x, mouse.y) var isOnKeyboard = keyboardPos.x >= 0 && keyboardPos.y >= 0 && keyboardPos.x <= keyboard.width && keyboardPos.y <= keyboard.height // 检查点击是否在当前目标输入框内 var isOnTarget = false if (keyboard.target) { var targetPos = mapToItem(keyboard.target, mouse.x, mouse.y) isOnTarget = targetPos.x >= 0 && targetPos.y >= 0 && targetPos.x <= keyboard.target.width && targetPos.y <= keyboard.target.height } if (!isOnKeyboard && !isOnTarget) keyboard.keyVisible = false else keyboard.keyVisible = true mouse.accepted = false } } Connections { target: csvLogger function onDownloadFile(status, err) { if (status) { usbCompletePopup.labText = "文件已拷贝到" + err; usbCompletePopup.open() } else { usbErrorPopup.labelText = err usbErrorPopup.open() } } function onFilesInfoCompleted(filefullData, fileMaxminData) { //console.log("========== 完整数据 ==========") //for (var i = 0; i < filefullData.length; i++) { // var channel = filefullData[i] //console.log("通道", i+1, ":", channel.channelName) //console.log(" 数据点数:", channel.points.length) // var printCount = Math.min(5, channel.points.length) // for (var j = 0; j < printCount; j++) { // var point = channel.points[j] //console.log(" 点", j+1, // "时间:", new Date(point.timestamp).toLocaleString(), // "值:", point.value) // } //if (channel.points.length > 5) { // console.log(" ...(省略", channel.points.length - 5, "个点)") //} //} // 打印最大最小值 //console.log("========== 数据范围 ==========") //for (var k = 0; k < fileMaxminData.length; k++) { //var range = fileMaxminData[k] //console.log("通道", k+1, ":", range.channelName) //console.log(" 时间范围:", // new Date(range.minTimestamp).toLocaleString(), // "-", // new Date(range.maxTimestamp).toLocaleString()) //console.log(" 值范围:", range.minValue, "-", range.maxValue) //} console.log("onFilesInfoCompleted 数据信息读取完成") chartPopup.channelDataList = filefullData chartPopup.channelDataMinMax = fileMaxminData chartPopup.open() } } function loadCsvFiles() { var csvFiles = csvLogger.getCsvFiles(); //console.log("文件数量:", csvFiles.length) fileListModel.clear(); for (var i = 0; i < csvFiles.length; i++) { var fileInfo = csvLogger.getCvsFileInfo(csvFiles[i]); //console.log("文件名称:", fileInfo.fileName, // " 是否为空:", fileInfo.isEmpty, // " 起始时间:", fileInfo.startTime, // " 文件全名:", csvFiles[i]) if (!fileInfo.isEmpty && fileInfo.startTime !== "") { fileListModel.append({ filePath: fileInfo.fullName, fileName: fileInfo.fileName, startTime: fileInfo.startTime, endTime: fileInfo.endTime, fileSize: fileInfo.fileSize, lineNumber: fileListModel.count + 1 }); } } } function handleDownloadRequest(fileName) { csvLogger.copyFile(fileName) } function handleRenameFile(filePath, newName) { csvLogger.renameFile(filePath, newName) } function analyzeCsvFile(filePath) { console.log("[analyzeCsvFile] 开始分析文件:", filePath) csvLogger.readChannelDataFromFile(filePath) } Popup { id: chartPopup width: 800 height: 500 anchors.centerIn: parent closePolicy: Popup.CloseOnEscape modal: true property bool isLarge: false property bool isTable: false property var channelDataList: [] property var channelDataMinMax: [] background: Rectangle { anchors.fill: parent color: "#212529" } Row { id: titleBar height: 30 anchors.top: parent.top anchors.right: parent.right anchors.rightMargin: 5 //anchors.margins: 5 spacing: 10 Button { text: chartPopup.isLarge ? "还原" : "放大" onClicked: { chartPopup.isLarge = !chartPopup.isLarge console.log("放大按钮被点击!可后续接入窗口放大逻辑: ", chartPopup.isLarge) // TODO: 后续可接入 Window 放大、全屏、最大化逻辑 } flat: true background: Rectangle { color: parent.pressed ? "#404040" : (parent.hovered ? "#353535" : "transparent") radius: 4 } contentItem: Text { text: parent.text color: "white" font.pixelSize: 14 } } Button { text: "关闭" onClicked: { console.log("点击弹窗关闭按钮") chartPopup.close() } flat: true background: Rectangle { color: parent.pressed ? "#404040" : (parent.hovered ? "#353535" : "transparent") radius: 4 } contentItem: Text { text: parent.text color: "white" font.pixelSize: 14 } } } Row { id: changeAera width: parent.width * 0.8 anchors.top: titleBar.bottom anchors.topMargin: 20 anchors.horizontalCenter: parent.horizontalCenter spacing: 2 Button { id: curveButton text: "曲线" background: Rectangle { color: "#B0E0E6" radius: 5 } onClicked: { console.log("点击曲线按钮") chartPopup.isTable = false } } Button { id: tableButton text: "图表" background: Rectangle { color: "#B0E0E6" radius: 5 } onClicked: { console.log("点击图表按钮") chartPopup.isTable = true } } } //显示区域 Item { id: dataAera width: parent.width - 40 height: parent.height - titleBar.height - changeAera.height - 60 anchors { top: changeAera.bottom left: parent.left margins: 20 topMargin: 10 } Component.onCompleted: { console.log("加载区域的大小,width:", parent.width, " height:", parent.height) } Rectangle { anchors.fill: parent color: "#444444" } // 动态内容加载 Loader { sourceComponent: chartPopup.isTable ? chartPage : curvePage onLoaded: { //console.log("[Loader] 已加载组件:", sourceComponent === curvePage ? "curvePage (含 CurvePlot)" : "chartPage (占位图)") } } } Component { id: curvePage CurvePlot { id: curvePlotComponent width: parent.width height: parent.height anchors.fill: parent channelDataList: chartPopup.channelDataList channelsMaxMins: chartPopup.channelDataMinMax } } Component { id: chartPage Rectangle { color: "#2E2E2E" anchors.fill: parent Text { text: "📊 图表/表格页面" color: "white" font.pixelSize: 18 anchors.centerIn: parent } } } //onChannelDataListChanged: { // console.log("弹窗收到数据,通道数 = ", channelDataList.length) //} } Component.onCompleted: { loadCsvFiles(); } // 背景层 Rectangle { id: background anchors.fill: parent color: "#000000" } // 顶部菜单栏 Rectangle { id: menuBar height: 50 * scaleY anchors { top: parent.top left: parent.left right: parent.right } color: "#212529" Image { id: homeButton source: "qrc:/images/backHomePage.svg" width: 36 * scaleX height: 36 * scaleY anchors { top: parent.top topMargin: 7 * scaleY left: parent.left leftMargin: 20 * scaleX } cache: true MouseArea { anchors.fill: parent onClicked: { mainLoader.source = "qml/StoragePage.qml" } } } Text { width: 60 * scaleX height: 40 * scaleY color: "#ffffffff" text: "监测记录" font { family: "TOPRIE-WR-YaHei" pixelSize: 28 * unifiedScale } anchors { verticalCenter: parent.verticalCenter horizontalCenter: parent.horizontalCenter } } Image { id: imageLayout source: "qrc:/images/layout.svg" width: 30 * scaleX height: 30 * scaleY anchors { top: parent.top topMargin: 10 * scaleY right: parent.right rightMargin: 20 * scaleX } cache: true MouseArea { anchors.fill: parent onClicked: { mainLoader.source = "qml/DataLogShowList.qml" } } } } // 主内容区 Rectangle { id: contentArea width: 984 * scaleX height: 521 * scaleY color: "transparent" anchors { top: menuBar.bottom bottom: parent.bottom left: parent.left right: parent.right rightMargin: 20 * scaleX topMargin: 14 * scaleY leftMargin: 20 * scaleX bottomMargin: 15 * scaleY } // 数据表格 Flickable { id: flickable anchors.fill: parent contentWidth: parent.width contentHeight: dataTable.height clip: true boundsBehavior: Flickable.StopAtBounds flickableDirection: Flickable.VerticalFlick Column { id: dataTable width: parent.width anchors { top: parent.top topMargin: 10 * scaleY } spacing: 20 * scaleY Repeater { model: fileListModel delegate: MonitorRecordItem { id: recordDelegate width: parent.width startTime: model.startTime endTime: model.endTime lineNumber: model.lineNumber customName: model.fileName fileSize: model.fileSize filePath: model.filePath vkeyboard: keyboard onDownloadRequested: handleDownloadRequest onAnalyzeRequested: analyzeCsvFile onRenameRequested: handleRenameFile } } } } } Popup { //导出完成提示 id: usbCompletePopup width: parent.width * 0.6 height: 120 anchors.centerIn: parent closePolicy: Popup.NoAutoClose background: Rectangle { color: "#212529" radius: 5 } property string labText: "" Column { width: parent.width * 0.8 anchors.horizontalCenter: parent.horizontalCenter spacing: 20 Label { width: parent.width text: usbCompletePopup.labText font.pixelSize: 20 color: "white" wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter } Label { width: parent.width text: "下载成功!" font.pixelSize: 22 color: "green" horizontalAlignment: Text.AlignHCenter } } Timer { id: closeTimer interval: 1500 running: usbCompletePopup.visible onTriggered: { usbCompletePopup.close() } } onOpened: closeTimer.restart() } Popup { //导出失败提示 id: usbErrorPopup width: parent.width * 0.6 height: 100 anchors.centerIn: parent closePolicy: Popup.NoAutoClose background: Rectangle { color: "#212529" radius: 5 } property string labelText: "" Label { anchors.centerIn: parent text: usbErrorPopup.labelText font.pixelSize: 22 color: "red" } Timer { id: errorTimer interval: 2000 running: usbErrorPopup.visible onTriggered: { usbErrorPopup.close() } } onOpened: errorTimer.restart() } } 获取数据并发送信号的函数 void CsvLogger::readChannelDataFromFile(const QString &filePath) { QVariantList channelFullDatas; QVariantList channelMaxminValues; channelFullDatas.clear(); channelMaxminValues.clear(); if (!QFile::exists(filePath)) { qDebug() << "File does not exist:" << filePath; return; } QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "无法打开文件:" << filePath; return; } QTextStream in(&file); QString headerLine = in.readLine().trimmed(); QStringList headers = headerLine.split(','); QList<QString> channelNames; // 解析通道名称 for (int i = 0; i < headers.size(); ++i) { QString field = headers[i].trimmed(); if (field.startsWith("\"") && field.endsWith("\"")) { field = field.mid(1, field.length() - 2); } if (i >= 2 && !field.isEmpty()) { channelNames.append(field); } } QVector<QVector<DataPoint>> channelDataVec(channelNames.size()); // 读取数据点 while (!in.atEnd()) { QString line = in.readLine().trimmed(); if (line.isEmpty()) continue; QStringList fields = line.split(','); if (fields.size() < 2 + channelNames.size()) continue; bool ok; QDateTime dt = QDateTime::fromString(fields[0], "yyyy/MM/dd hh:mm:ss"); if (!dt.isValid()) continue; qint64 timestamp = dt.toMSecsSinceEpoch(); for (int i = 0; i < channelNames.size(); ++i) { float value = fields[2 + i].toFloat(&ok); if (ok) { channelDataVec[i].append({timestamp, value}); } } } // 处理每个通道数据 for (int i = 0; i < channelNames.size(); ++i) { QString channelName = channelNames[i]; const QVector<DataPoint> &points = channelDataVec[i]; if (points.isEmpty()) { continue; } // 准备完整数据 QVariantMap channelMap; channelMap["channelName"] = channelName; QVariantList pointsList; for (const DataPoint &dp : points) { QVariantMap dpMap; dpMap["timestamp"] = qint64(dp.timestamp); dpMap["value"] = double(dp.value); pointsList.append(dpMap); } channelMap["points"] = pointsList; channelFullDatas.append(channelMap); // 计算并存储最大最小值 qint64 minTimestamp = points[0].timestamp; qint64 maxTimestamp = points[0].timestamp; float minValue = points[0].value; float maxValue = points[0].value; for (const DataPoint &dp : points) { if (dp.timestamp < minTimestamp) minTimestamp = dp.timestamp; if (dp.timestamp > maxTimestamp) maxTimestamp = dp.timestamp; if (dp.value < minValue) minValue = dp.value; if (dp.value > maxValue) maxValue = dp.value; } QVariantMap maxminMap; maxminMap["channelName"] = channelName; maxminMap["minTimestamp"] = minTimestamp; maxminMap["maxTimestamp"] = maxTimestamp; maxminMap["minValue"] = minValue; maxminMap["maxValue"] = maxValue; channelMaxminValues.append(maxminMap); } file.close(); emit filesInfoCompleted(channelFullDatas, channelMaxminValues); } 在点击分析按钮之前,channelDataListchannelsMaxMins为空,这是正常的,当点击分析按钮后,会调用readChannelDataFromFile获取文件的内容,然后发信息给qml,给channelDataListchannelsMaxMins赋值 目前qml点击分析按钮,会弹窗窗口,但是窗口中并没有绘制数据曲线部分。其运行打印日志内容如下: 按照上面修改以后,还是没有曲线生成,打印内容如下:qml: 检测到channelDataList变化,重新验证数据... qml: 数据无效:channelDataList为空 qml: 尝试触发绘制 | 尺寸有效:false 数据有效:false 当前尺寸:0x0 qml: 暂不满足绘制条件,等待布局或数据更新... qml: CurvePlot初始化完成,初始尺寸: 0 x 0 qml: Canvas布局完成,父容器实际尺寸: 0 x 0 qml: 加载区域的大小,width: 776 height: 476 qml: 监测记录 clicked qml: 尝试触发绘制 | 尺寸有效:true 数据有效:false 当前尺寸:0x0 qml: 暂不满足绘制条件,等待布局或数据更新... qml: 尝试触发绘制 | 尺寸有效:true 数据有效:false 当前尺寸:0x0 qml: 暂不满足绘制条件,等待布局或数据更新... qml: 尝试触发绘制 | 尺寸有效:true 数据有效:false 当前尺寸:0x0 qml: 暂不满足绘制条件,等待布局或数据更新... qml: 数据验证失败超过3次,停止重试 qml: [analyzeCsvFile] 开始分析文件: D:/kjl/code/7inchUIDemo/build/dataLog/eeeeeee.csv qml: onFilesInfoCompleted 数据信息读取完成 qml: 检测到channelDataList变化,重新验证数据... qml: 数据无效:channelsMaxMins为空 qml: 尝试触发绘制 | 尺寸有效:true 数据有效:false 当前尺寸:0x0 qml: 暂不满足绘制条件,等待布局或数据更新... qml: 尝试触发绘制 | 尺寸有效:true 数据有效:false 当前尺寸:0x0 qml: 暂不满足绘制条件,等待布局或数据更新... qml: 尝试触发绘制 | 尺寸有效:true 数据有效:false 当前尺寸:0x0 qml: 暂不满足绘制条件,等待布局或数据更新... qml: 数据验证失败超过3次,停止重试
08-12
请问如何S地图层级控制:「『 // qrc:/qml/common/DiscoverPage/PersonalRidingPage.qml import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import QtPositioning 5.15 import Qt5Compat.GraphicalEffects import QtWebView 1.15 import "qrc:/qml/common/DiscoverPage/" import App.Models 1.0 import RadioApp.Permission 1.0 Page { id: root background: Rectangle { color: "#0f0f14" } // 使用 RidingDataModel 的属性 property real todayTotalDistance: RidingDataModel ? RidingDataModel.todayTotalDistance : 0.0 property real currentRideDistance: RidingDataModel ? RidingDataModel.currentDistance / 1000.0 : 0.0 // 米转公里 property real currentSpeed: RidingDataModel ? RidingDataModel.currentSpeed * 3.6 : 0.0 // m/s 转 km/h property real maxSpeed: RidingDataModel ? RidingDataModel.maxSpeed * 3.6 : 0.0 // m/s 转 km/h property real maxAltitude: RidingDataModel ? RidingDataModel.maxAltitude : 0.0 property int ridingTime: RidingDataModel ? RidingDataModel.ridingTime : 0 property string formattedRidingTime: formatTime(ridingTime) // 从 RidingDataModel 获取状态 property bool isRiding: RidingDataModel ? RidingDataModel.isRiding : false property bool isPaused: RidingDataModel ? RidingDataModel.isPaused : false property string lastRecordId: RidingDataModel ? RidingDataModel.recordId : "" // 状态标志 property bool isDestroyed: false property bool isInitialized: false property bool permissionsGranted: false property bool permissionsChecking: false // 地图控制按钮属性 property string btnBlue: "#007bff" property real btnSize: 0.6 property bool requestCenter: false // JavaScript 通信相关的属性 - 修改为直接使用 HTTP URL //property string mapUrl: "https://www.xjsfly.com/radio_map/map.html" // 直接使用网络URL property string mapUrl: "http://119.91.39.85/maps/map.html" // 直接使用网络URL property bool mapLoadedFromWeb: false property bool mapLoading: false // 平台特定调整 property real platformScale: Qt.platform.os === "android" ? 1.2 : 1.0 property real buttonTopMargin: Qt.platform.os === "android" ? 50 : 30 property real buttonSideMargin: Qt.platform.os === "android" ? 25 : 20 // 权限管理器 property PermissionManager permissionManager: PermissionManager { id: permissionManager onPermissionGranted: { console.log("权限已授予:", permissionTypeToString(permissionType)) checkAllRequiredPermissions() } onPermissionDenied: { console.log("权限被拒绝:", permissionTypeToString(permissionType)) showPermissionDeniedDialog(permissionType) } onAllRequiredPermissionsGranted: { console.log("所有必需权限已授予") permissionsGranted = true permissionsChecking = false // 权限就绪后继续初始化 if (!isInitialized) { continueInitialization() } } onEssentialPermissionsGranted: { console.log("核心权限已授予") permissionsGranted = true } onShowPermissionRationale: { console.log("显示权限解释:", rationale) showPermissionRationaleDialog(permissionType, rationale) } onShowPermissionExplanation: { console.log("显示权限说明:", title, message) showPermissionExplanationDialog(permissionType, title, message) } } // 时间格式化函数 function formatTime(seconds) { if (isDestroyed) return "00:00"; var hours = Math.floor(seconds / 3600); var minutes = Math.floor((seconds % 3600) / 60); var secs = seconds % 60; if (hours > 0) { return hours + ":" + (minutes < 10 ? "0" + minutes : minutes) + ":" + (secs < 10 ? "0" + secs : secs); } else { return (minutes < 10 ? "0" + minutes : minutes) + ":" + (secs < 10 ? "0" + secs : secs); } } // 权限检查函数 function checkLocationPermission() { if (isDestroyed) return false; if (positionSource.supportedPositioningMethods === PositionSource.NoPositioningMethods) { console.error("备不支持定位功能"); showPermissionDialog("定位功能不可用", "您的备不支持定位功能,请检查置。"); return false; } if (positionSource.active === false) { console.warn("定位服务未激活"); showPermissionDialog("定位权限", "请确保已授予应用定位权限,并在系统置中开启定位服务。"); return false; } return true; } // 检查所有必需的权限 function checkAllRequiredPermissions() { console.log("开始检查所有必需权限...") permissionsChecking = true // 使用 PermissionManager 的枚举值 var locationGranted = permissionManager.checkPermission(permissionManager.LocationPermission) var coarseLocationGranted = permissionManager.checkPermission(permissionManager.CoarseLocationPermission) var backgroundLocationGranted = permissionManager.checkPermission(permissionManager.BackgroundLocationPermission) console.log("定位权限状态 - 精确定位:", locationGranted, "粗略定位:", coarseLocationGranted, "后台定位:", backgroundLocationGranted) if (locationGranted && coarseLocationGranted) { permissionsGranted = true permissionsChecking = false console.log("定位权限已授予,继续初始化") if (!isInitialized) { continueInitialization() } } else { console.log("定位权限未完全授予,请求权限...") requestLocationPermissions() } } // 请求定位相关权限 function requestLocationPermissions() { if (isDestroyed) return; console.log("请求定位相关权限...") // 使用智能上下文请求权限 permissionManager.requestSmartPermissions("navigation_app") } // 权限类型转字符串 function permissionTypeToString(permissionType) { // 这里 permissionType 已经是数字值,直接比较即可 if (permissionType === permissionManager.LocationPermission) return "精确定位" if (permissionType === permissionManager.CoarseLocationPermission) return "粗略定位" if (permissionType === permissionManager.BackgroundLocationPermission) return "后台定位" if (permissionType === permissionManager.CameraPermission) return "相机" if (permissionType === permissionManager.MicrophonePermission) return "麦克风" if (permissionType === permissionManager.StoragePermission) return "存储" if (permissionType === permissionManager.BluetoothPermission) return "蓝牙" return "未知权限" } // 显示权限解释对话框 function showPermissionRationaleDialog(permissionType, rationale) { if (isDestroyed) return; var component = Qt.createComponent("qrc:/qml/common/PermissionRationaleDialog.qml"); if (component.status === Component.Ready) { var dialog = component.createObject(root, { permissionType: permissionType, rationale: rationale, permissionManager: permissionManager }); dialog.open(); } else { // 回退到通用对话框 showPermissionDialog("权限说明", rationale) } } // 显示权限说明对话框 function showPermissionExplanationDialog(permissionType, title, message) { if (isDestroyed) return; showPermissionDialog(title, message) } // 显示权限被拒绝对话框 function showPermissionDeniedDialog(permissionType) { if (isDestroyed) return; var permissionName = permissionTypeToString(permissionType) var message = permissionName + "权限被拒绝,相关功能将无法正常使用。" var suggestion = "请前往应用置中手动启用此权限" showPermissionDialog("权限被拒绝", message + "\n\n" + suggestion) } // 通用权限对话框 function showPermissionDialog(title, message) { if (isDestroyed) return; var component = Qt.createComponent("qrc:/qml/common/CommonDialog.qml"); if (component.status === Component.Ready) { var dialog = component.createObject(root, { title: title, message: message }); dialog.open(); } } // 继续初始化(权限检查通过后) function continueInitialization() { if (isDestroyed || isInitialized) return; console.log("权限检查通过,继续初始化...") console.log("使用新的布局结构 - 地图区域:", mapContainer.width + "x" + mapContainer.height) console.log("按钮覆盖层区域:", buttonsOverlay.width + "x" + buttonsOverlay.height) // 直接使用网络URL加载地图 loadMapFromWeb() initTimer.start() // 安卓上延迟调试按钮布局 if (Qt.platform.os === "android") { Qt.callLater(function() { if (!isDestroyed) { debugButtonLayout() // 安卓上可能需要额外的刷新 Qt.callLater(forceButtonsRefresh) } }) } } // 从网络加载地图文件 function loadMapFromWeb() { if (isDestroyed) return; console.log("使用网络URL加载地图...") mapLoading = true // 直接使用网络URL console.log("置地图 URL:", mapUrl) webView.url = mapUrl } // 更新统计数据 function updateStats() { if (!permissionsGranted) return; todayTotalDistance = RidingDataModel ? RidingDataModel.todayTotalDistance : 0.0; currentRideDistance = RidingDataModel ? RidingDataModel.currentDistance / 1000.0 : 0.0; currentSpeed = RidingDataModel ? RidingDataModel.currentSpeed * 3.6 : 0.0; maxSpeed = RidingDataModel ? RidingDataModel.maxSpeed * 3.6 : 0.0; maxAltitude = RidingDataModel ? RidingDataModel.maxAltitude : 0.0; ridingTime = RidingDataModel ? RidingDataModel.ridingTime : 0; console.log("更新统计数据:", "今日总距离:", todayTotalDistance.toFixed(2), "当前距离:", currentRideDistance.toFixed(2), "当前速度:", currentSpeed.toFixed(1), "最高速度:", maxSpeed.toFixed(1), "最高海拔:", maxAltitude.toFixed(0)); } // JavaScript 调用函数 function executeJavaScript(code) { if (isDestroyed || !webView || !permissionsGranted) return; try { webView.runJavaScript(code); } catch (error) { console.error("执行 JavaScript 失败:", error); } } // 处理来自 JavaScript 的消息 function handleJavaScriptMessage(url) { if (isDestroyed || !permissionsGranted || !url) return; var urlString = url.toString(); if (urlString.indexOf("qtbridge://") === -1) return; try { var message = urlString.split("qtbridge://")[1]; var parts = message.split("/"); var action = parts[0].toLowerCase(); // 转换为小写统一处理 var data = parts.length > 1 ? parts[1] : ""; console.log("收到 JavaScript 消息:", action, data); switch(action) { case "mapready": console.log("高德地图初始化完成"); handleMapReady(); break; case "distancecalculated": var distance = parseFloat(data); console.log("JavaScript计算距离:", distance, "米"); handleDistanceCalculated(distance); break; case "positionupdated": var coords = data.split(","); var lat = parseFloat(coords[0]); var lng = parseFloat(coords[1]); console.log("地图位置更新:", lat, lng); break; case "error": console.error("JavaScript错误:", decodeURIComponent(data)); break; default: console.log("未知消息类型:", action); } } catch (error) { console.error("处理JavaScript消息失败:", error); } } // 清除地图轨迹 function clearMapTrack() { executeJavaScript(` if (window.clearTrack) { window.clearTrack(); } else if (window.qtTrackLine && window.qtMap) { window.qtTrackPoints = []; window.qtTrackLine.setPath([]); window.qtTotalDistance = 0; console.log("轨迹已清除"); } `); } // 添加轨迹点 function addTrackPoint(latitude, longitude) { executeJavaScript(` if (window.addTrackPoint) { window.addTrackPoint(${latitude}, ${longitude}); } else if (window.qtTrackLine && window.qtMap && window.qtMarker) { try { var point = new AMap.LngLat(${longitude}, ${latitude}); window.qtTrackPoints.push(point); window.qtTrackLine.setPath(window.qtTrackPoints); // 更新标记位置 window.qtMarker.setPosition(point); // 计算距离 if (window.qtTrackPoints.length > 1) { var total = 0; for (var i = 1; i < window.qtTrackPoints.length; i++) { total += window.qtTrackPoints[i-1].distance(window.qtTrackPoints[i]); } window.qtTotalDistance = total; // 发送距离到Qt window.location.href = "qtbridge://distanceCalculated/" + total; } // 自动调整地图视野 if (window.qtTrackPoints.length === 1) { window.qtMap.setCenter(point); } else { window.qtMap.setFitView(); } console.log("添加轨迹点:", ${latitude}, ${longitude}); } catch (error) { console.error("添加轨迹点失败:", error); } } `); } // 置地图中心 function setMapCenter(latitude, longitude) { executeJavaScript(` if (window.setMapCenter) { window.setMapCenter(${latitude}, ${longitude}); } else if (window.qtMap && window.qtMarker) { try { var point = new AMap.LngLat(${longitude}, ${latitude}); window.qtMap.setCenter(point); window.qtMarker.setPosition(point); console.log("置地图中心:", ${latitude}, ${longitude}); } catch (error) { console.error("置地图中心失败:", error); } } `); } // 处理JavaScript计算的距离 function handleDistanceCalculated(distance) { if (isDestroyed || !RidingDataModel) return; console.log("收到JavaScript计算的距离:", distance, "米"); RidingDataModel.updateDistance(distance); } // 处理地图就绪 function handleMapReady() { if (isDestroyed) return; console.log("高德地图准备就绪"); // 地图就绪后可以执行一些初始化操作 } // 调试按钮布局 function debugButtonLayout() { if (isDestroyed) return console.log("=== 按钮布局调试信息 ===") console.log("平台:", Qt.platform.os) console.log("屏幕尺寸:", root.width, "x", root.height) console.log("地图容器尺寸:", mapContainer.width, "x", mapContainer.height) console.log("按钮覆盖层尺寸:", buttonsOverlay.width, "x", buttonsOverlay.height) console.log("按钮覆盖层z值:", buttonsOverlay.z) console.log("WebView z值:", webView.z) var buttons = [ {name: "速度按钮", obj: speedButton}, {name: "功能按钮组", obj: functionButtons}, {name: "中心按钮", obj: centerButton}, {name: "倾角按钮", obj: tiltButton}, {name: "打卡按钮", obj: checkinButton} ] buttons.forEach(function(btn) { if (btn.obj) { console.log(btn.name + ":", "可见:", btn.obj.visible, "位置:", btn.obj.x + "," + btn.obj.y, "尺寸:", btn.obj.width + "x" + btn.obj.height, "z值:", btn.obj.z, "图层启用:", btn.obj.layer ? btn.obj.layer.enabled : "N/A") } else { console.log(btn.name + ": 未定义") } }) } // 强制刷新按钮显示 function forceButtonsRefresh() { if (isDestroyed) return console.log("强制刷新按钮显示状态") // 临时隐藏再显示,强制重绘 var buttons = [speedButton, functionButtons, centerButton, tiltButton, checkinButton] buttons.forEach(function(button) { if (button) { var wasVisible = button.visible button.visible = false Qt.callLater(function() { if (button && !isDestroyed) { button.visible = wasVisible console.log("刷新按钮:", button.objectName || "未知", "位置:", button.x, button.y) } }) } }) } // 在 Component.onCompleted 中添加更多调试信息 Component.onCompleted: { console.log("个人骑行页面初始化开始"); console.log("RidingDataModel 可用:", RidingDataModel !== undefined); console.log("PermissionManager 可用:", permissionManager !== undefined); console.log("地图URL:", mapUrl); console.log("WebView 组件状态:", webView !== undefined); console.log("当前平台:", Qt.platform.os); // 安卓特定初始化 if (Qt.platform.os === "android") { console.log("安卓平台检测到,应用特定优化") // 调整按钮大小位置以适应安卓 btnSize = 0.8 } // 调试权限类型 permissionManager.debugPermissionTypes(); // 首先检查权限 checkAllRequiredPermissions(); } Component.onDestruction: { console.log("个人骑行页面开始销毁"); isDestroyed = true; // 停止所有定时器 statsTimer.stop(); initTimer.stop(); positionSource.active = false; console.log("个人骑行页面销毁完成"); } // 延迟初始化定时器 Timer { id: initTimer interval: 500 running: false repeat: false onTriggered: { if (isDestroyed || !permissionsGranted) return; console.log("执行延迟初始化"); updateStats(); // 启动统计更新定时器 statsTimer.start(); isInitialized = true; console.log("个人骑行页面初始化完成"); } } // 定时器用于定期更新统计 Timer { id: statsTimer interval: 1000 running: false repeat: true onTriggered: { if (!isDestroyed && isInitialized && permissionsGranted) { updateStats(); } } } // 权限检查覆盖层 Rectangle { id: permissionOverlay anchors.fill: parent color: "#CC000000" visible: !permissionsGranted || permissionsChecking z: 1000 Column { anchors.centerIn: parent spacing: 20 BusyIndicator { id: permissionBusyIndicator running: permissionsChecking width: 50 height: 50 anchors.horizontalCenter: parent.horizontalCenter } Text { text: permissionsChecking ? "正在检查权限..." : "需要定位权限才能使用骑行功能" color: "white" font.pixelSize: 16 anchors.horizontalCenter: parent.horizontalCenter } Button { text: "请求权限" visible: !permissionsChecking && !permissionsGranted anchors.horizontalCenter: parent.horizontalCenter onClicked: { requestLocationPermissions() } } Text { text: "骑行功能需要精确定位权限来记录您的轨迹位置" color: "#CCCCCC" font.pixelSize: 14 width: parent.width * 0.8 wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter } } } // 地图加载状态覆盖层 Rectangle { id: mapLoadingOverlay anchors.fill: parent color: "#CC000000" visible: mapLoading z: 999 Column { anchors.centerIn: parent spacing: 20 BusyIndicator { id: mapLoadingIndicator running: mapLoading width: 50 height: 50 anchors.horizontalCenter: parent.horizontalCenter } Text { text: "正在加载地图..." color: "white" font.pixelSize: 16 anchors.horizontalCenter: parent.horizontalCenter } Text { text: "请确保网络连接正常" color: "#CCCCCC" font.pixelSize: 14 width: parent.width * 0.8 wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter } // 添加重试按钮 Button { text: "重试加载" visible: !mapLoadingIndicator.running && mapLoading anchors.horizontalCenter: parent.horizontalCenter onClicked: { console.log("手动重试地图加载") webView.reload() } } } } // 状态变化监听 - 监听 RidingDataModel Connections { id: ridingDataModelConnections target: RidingDataModel enabled: !isDestroyed && isInitialized && RidingDataModel !== undefined && permissionsGranted function onIsRidingChanged() { if (isDestroyed) return; console.log("骑行状态改变 - 是否骑行:", RidingDataModel.isRiding); Qt.callLater(updateStats); } function onIsPausedChanged() { if (isDestroyed) return; console.log("暂停状态改变 - 是否暂停:", RidingDataModel.isPaused); Qt.callLater(updateStats); } function onCurrentDistanceChanged() { if (isDestroyed) return; Qt.callLater(updateStats); } function onCurrentSpeedChanged() { if (isDestroyed) return; Qt.callLater(updateStats); } function onRidingTimeChanged() { if (isDestroyed) return; Qt.callLater(updateStats); } function onMaxSpeedChanged() { if (isDestroyed) return; Qt.callLater(updateStats); } function onMaxAltitudeChanged() { if (isDestroyed) return; Qt.callLater(updateStats); } function onTodayTotalDistanceChanged() { if (isDestroyed) return; Qt.callLater(updateStats); } } // 主要修改:使用Column布局分离地图数据面板 Item { anchors.fill: parent enabled: permissionsGranted opacity: permissionsGranted ? 1.0 : 0.3 // 使用Column布局分离地图数据面板 Column { anchors.fill: parent spacing: 0 // 地图区域 - 固定高度 Item { id: mapContainer width: parent.width height: parent.height * 28/33 z: 0 Component.onCompleted: { console.log("地图容器创建完成,尺寸:", width, "x", height) } // 使用 WebView 显示地图 WebView { id: webView anchors.fill: parent visible: !isDestroyed && permissionsGranted && !mapLoading url: mapUrl z: 0 Component.onCompleted: { if (Qt.platform.os === "android") { console.log("安卓 WebView 配置: 启用混合内容文件访问"); } } onLoadingChanged: function(loadRequest) { if (isDestroyed || !permissionsGranted) return; console.log("=== 页面加载状态 ==="); console.log("当前平台:", Qt.platform.os); console.log("页面加载状态:", loadRequest.status); console.log("页面URL:", webView.url); console.log("请求URL:", loadRequest.url); console.log("错误信息:", loadRequest.errorString); if (loadRequest.url.toString().indexOf("qtbridge://") === 0) { console.log("忽略 qtbridge 通信请求,状态:", loadRequest.status); return; } switch(loadRequest.status) { case WebView.LoadStartedStatus: console.log("开始加载地图页面"); mapLoading = true; break; case WebView.LoadSucceededStatus: console.log("地图页面加载成功"); mapLoading = false; mapLoadedFromWeb = true; // Android上尝试置透明背景 if (Qt.platform.os === "android") { Qt.callLater(function() { if (!isDestroyed) { webView.runJavaScript(` try { document.body.style.backgroundColor = 'transparent'; document.body.style.overflow = 'hidden'; if (document.querySelector('.amap-container')) { document.querySelector('.amap-container').style.backgroundColor = 'transparent'; } } catch(e) { console.log('置透明背景失败:', e); } `); } }); } if (Qt.platform.os === "android") { Qt.callLater(forceButtonsRefresh) } break; case WebView.LoadFailedStatus: console.error("地图页面加载失败:", loadRequest.errorString); console.error("错误详情:", { url: loadRequest.url, errorString: loadRequest.errorString }); mapLoading = false; showMapLoadError(loadRequest.errorString); break; } } onUrlChanged: { if (isDestroyed || !permissionsGranted) return; var urlString = webView.url.toString(); console.log("URL改变:", urlString); if (urlString.indexOf("qtbridge://") === 0) { handleJavaScriptMessage(webView.url); } } } // 定位源 PositionSource { id: positionSource active: false updateInterval: 1000 preferredPositioningMethods: PositionSource.AllPositioningMethods onPositionChanged: { if (isDestroyed || !permissionsGranted) { active = false; return; } if (requestCenter && position.latitudeValid && position.longitudeValid) { var coord = position.coordinate; setMapCenter(coord.latitude, coord.longitude); requestCenter = false; active = false; console.log("回到中心位置:", coord.latitude, coord.longitude); } } onSourceErrorChanged: { if (isDestroyed || !permissionsGranted) return; if (sourceError !== PositionSource.NoError) { console.error("定位错误:", sourceError); requestCenter = false; active = false; } } } } // 运动数据区域 - 固定高度 Rectangle { id: dataPanel width: parent.width height: parent.height * 5/33 color: "#333333" z: 10 // 确保在按钮层之上 visible: !isDestroyed && permissionsGranted property real rowSpacing: 10 property real columnSpacing: 10 property real topMargin: 10 property real valueFontSize: 21 property real labelFontSize: 10 ColumnLayout { anchors.fill: parent anchors.margins: dataPanel.topMargin spacing: dataPanel.rowSpacing Item { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: parent.width * 3/4 Layout.fillHeight: true Row { anchors.fill: parent spacing: dataPanel.columnSpacing // 今日总里程 Column { width: parent.width / 3 height: parent.height spacing: 2 Text { text: isDestroyed ? "0.00" : todayTotalDistance.toFixed(2) color: "white" font.pixelSize: dataPanel.valueFontSize font.bold: true anchors.horizontalCenter: parent.horizontalCenter } Text { text: "今日总里程(km)" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.horizontalCenter: parent.horizontalCenter } } // 本次骑行里程 Column { width: parent.width / 3 height: parent.height spacing: 2 Text { text: isDestroyed ? "0.00" : currentRideDistance.toFixed(2) color: "white" font.pixelSize: dataPanel.valueFontSize font.bold: true anchors.horizontalCenter: parent.horizontalCenter } Text { text: "本次骑行里程(km)" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.horizontalCenter: parent.horizontalCenter } } // 本次骑行时间 Column { width: parent.width / 3 height: parent.height spacing: 2 Text { text: isDestroyed ? "00:00" : formattedRidingTime color: "white" font.pixelSize: dataPanel.valueFontSize font.bold: true anchors.horizontalCenter: parent.horizontalCenter } Text { text: "本次骑行时间" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.horizontalCenter: parent.horizontalCenter } } } } Item { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: parent.width * 3/5 Layout.fillHeight: true Row { anchors.fill: parent spacing: dataPanel.columnSpacing Row { width: parent.width / 2 height: parent.height spacing: 5 anchors.verticalCenter: parent.verticalCenter Text { text: "最高海拔" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.verticalCenter: parent.verticalCenter } Text { text: isDestroyed ? "0" : maxAltitude.toFixed(0) color: "white" font.pixelSize: dataPanel.valueFontSize font.bold: true anchors.verticalCenter: parent.verticalCenter } Text { text: "m" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.verticalCenter: parent.verticalCenter } } Row { width: parent.width / 2 height: parent.height spacing: 5 anchors.verticalCenter: parent.verticalCenter Text { text: "最高车速" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.verticalCenter: parent.verticalCenter } Text { text: isDestroyed ? "0.0" : maxSpeed.toFixed(1) color: "white" font.pixelSize: dataPanel.valueFontSize font.bold: true anchors.verticalCenter: parent.verticalCenter } Text { text: "km/h" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.verticalCenter: parent.verticalCenter } } } } } } } // 按钮覆盖层 - 只覆盖地图区域,不覆盖数据面板 Item { id: buttonsOverlay anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right height: mapContainer.height // 只覆盖地图区域 z: 999 // 置为最高层级 enabled: !isDestroyed && permissionsGranted Component.onCompleted: { console.log("按钮覆盖层创建完成,尺寸:", width, "x", height) console.log("地图容器高度:", mapContainer.height) debugButtonLayout() } // 1. 左上角:速度显示按钮 Rectangle { id: speedButton width: 120 * btnSize * platformScale height: 120 * btnSize * platformScale radius: height/2 color: btnBlue x: buttonSideMargin y: buttonTopMargin visible: !isDestroyed && permissionsGranted z: 1000 // 最高优先级 // 添加强制渲染属性 layer.enabled: true layer.smooth: true Component.onCompleted: { console.log("速度按钮创建完成,位置:", x, y, "尺寸:", width, height) } Column { anchors.centerIn: parent spacing: 0 Text { text: isDestroyed ? "0.0" : Math.min(currentSpeed.toFixed(1), 99.9) color: "white" font.pixelSize: 19 * platformScale font.bold: true anchors.horizontalCenter: parent.horizontalCenter } Text { text: "km/h" color: "white" font.pixelSize: 14 * 0.8 * platformScale anchors.horizontalCenter: parent.horizontalCenter } } } // 2. 右上角:功能按钮组 Rectangle { id: functionButtons width: 60 * platformScale height: 240 * platformScale radius: 8 color: "#CC000000" x: parent.width - width - buttonSideMargin y: buttonTopMargin visible: !isDestroyed && permissionsGranted z: 1000 Component.onCompleted: { console.log("功能按钮组创建完成,位置:", x, y, "尺寸:", width, height) } Column { anchors.fill: parent spacing: 0 FunctionButton { width: parent.width height: parent.height / 5 iconSource: "qrc:/assets/icons/discover/floating.png" tooltip: "悬浮窗功能" iconWidth: parent.width - 10 iconHeight: parent.height / 5 - 20 containerPadding: 0 enableOpacityEffect: true enableScaleEffect: true enableColorEffect: true onClicked: { if (!isDestroyed) console.log("悬浮窗功能") } } Rectangle { width: parent.width * 2 / 3 height: 1 anchors.horizontalCenter: parent.horizontalCenter color: "#80FFFFFF" } // 暂停/继续按钮 FunctionButton { width: parent.width height: parent.height / 5 iconSource: { if (isDestroyed || !RidingDataModel) return "qrc:/assets/icons/pause.png"; return RidingDataModel.isPaused ? "qrc:/assets/icons/play.png" : "qrc:/assets/icons/pause.png"; } tooltip: { if (isDestroyed || !RidingDataModel) return "暂停"; return RidingDataModel.isPaused ? "继续" : "暂停"; } iconWidth: parent.width - 10 iconHeight: parent.height / 5 - 20 containerPadding: 0 enableOpacityEffect: true enableScaleEffect: true enableColorEffect: true onClicked: { if (isDestroyed || !RidingDataModel || !permissionsGranted) return; try { if (RidingDataModel.isRiding && !RidingDataModel.isPaused) { RidingDataModel.pauseRiding(); } else if (RidingDataModel.isRiding && RidingDataModel.isPaused) { RidingDataModel.resumeRiding(); } } catch (error) { console.error("操作骑行状态失败:", error); } } } Rectangle { width: parent.width * 2 / 3 height: 1 anchors.horizontalCenter: parent.horizontalCenter color: "#80FFFFFF" } FunctionButton { width: parent.width height: parent.height / 5 iconSource: "qrc:/assets/icons/discover/friends.png" tooltip: "交友功能" iconWidth: parent.width - 10 iconHeight: parent.height / 5 - 20 containerPadding: 0 enableOpacityEffect: true enableScaleEffect: true enableColorEffect: true onClicked: { if (!isDestroyed) console.log("交友功能") } } Rectangle { width: parent.width * 2 / 3 height: 1 anchors.horizontalCenter: parent.horizontalCenter color: "#80FFFFFF" } FunctionButton { width: parent.width height: parent.height / 5 iconSource: "qrc:/assets/icons/discover/vlink.png" tooltip: "已链接状态" iconWidth: parent.width - 10 iconHeight: parent.height / 5 - 20 containerPadding: 0 enableOpacityEffect: true enableScaleEffect: true enableColorEffect: true onClicked: { if (!isDestroyed) console.log("已链接状态") } } Rectangle { width: parent.width * 2 / 3 height: 1 anchors.horizontalCenter: parent.horizontalCenter color: "#80FFFFFF" } // 分享按钮 FunctionButton { width: parent.width height: parent.height / 5 iconSource: "qrc:/assets/icons/discover/share.png" tooltip: "分享轨迹" iconWidth: parent.width - 10 iconHeight: parent.height / 5 - 20 containerPadding: 0 enableOpacityEffect: true enableScaleEffect: true enableColorEffect: true onClicked: { if (isDestroyed || !permissionsGranted) return; console.log("分享轨迹"); } } } } // 3. 回到中心按钮 Rectangle { id: centerButton width: functionButtons.width height: functionButtons.width x: parent.width - width - buttonSideMargin y: functionButtons.y + functionButtons.height + 30 radius: 8 color: "#CC000000" visible: !isDestroyed && permissionsGranted z: 1000 Component.onCompleted: { console.log("中心按钮创建完成,位置:", x, y) } FunctionButton { anchors.fill: parent iconSource: "qrc:/assets/icons/discover/location.png" iconWidth: 30 iconHeight: 30 containerPadding: (parent.width - 24) / 2 enableOpacityEffect: true enableScaleEffect: true enableColorEffect: true normalColor: "white" pressedColor: "#4d8eff" onClicked: { if (isDestroyed || !permissionsGranted) return; console.log("回到中心位置"); requestCenter = true; positionSource.active = true; } } } // 4. 左下角:记录倾角按钮 Rectangle { id: tiltButton width: 100 * btnSize * platformScale height: 100 * btnSize * platformScale radius: height/2 color: btnBlue x: buttonSideMargin y: parent.height - height - buttonSideMargin z: 1000 visible: !isDestroyed && permissionsGranted // 添加强制渲染属性 layer.enabled: true layer.smooth: true Component.onCompleted: { console.log("倾角按钮创建完成,位置:", x, y) } scale: mouseAreaTilt.pressed ? 0.95 : 1.0 opacity: mouseAreaTilt.pressed ? 0.95 : 1.0 Behavior on scale { enabled: !isDestroyed NumberAnimation { duration: 150; easing.type: Easing.InOutQuad } } Behavior on opacity { enabled: !isDestroyed NumberAnimation { duration: 150; easing.type: Easing.InOutQuad } } Text { text: "记录倾角" color: "white" font.pixelSize: 13 * platformScale anchors.centerIn: parent } MouseArea { id: mouseAreaTilt anchors.fill: parent onClicked: { if (!isDestroyed && permissionsGranted) console.log("记录倾角功能") } } } // 5. 右下角:打卡按钮 Rectangle { id: checkinButton width: 120 * btnSize * platformScale height: 50 * btnSize * platformScale radius: 8 color: btnBlue x: parent.width - width - buttonSideMargin y: parent.height - height - buttonSideMargin visible: !isDestroyed && permissionsGranted z: 1000 // 添加强制渲染属性 layer.enabled: true layer.smooth: true Component.onCompleted: { console.log("打卡按钮创建完成,位置:", x, y) } scale: mouseAreaCheckin.pressed ? 0.95 : 1.0 opacity: mouseAreaCheckin.pressed ? 0.95 : 1.0 Behavior on scale { enabled: !isDestroyed NumberAnimation { duration: 150; easing.type: Easing.InOutQuad } } Behavior on opacity { enabled: !isDestroyed NumberAnimation { duration: 150; easing.type: Easing.InOutQuad } } Text { text: { if (isDestroyed || !RidingDataModel) return "开始记录"; if (!RidingDataModel.isRiding) return "开始记录"; return RidingDataModel.isPaused ? "继续记录" : "停止记录"; } color: "white" font.pixelSize: 16 * platformScale anchors.centerIn: parent } MouseArea { id: mouseAreaCheckin anchors.fill: parent onClicked: { if (isDestroyed || !RidingDataModel) { console.error("RidingDataModel 未定义"); return; } if (!permissionsGranted) { showPermissionDialog("权限提示", "请先授予定位权限以使用骑行记录功能"); requestLocationPermissions(); return; } if (!checkLocationPermission()) { return; } try { if (!RidingDataModel.isRiding) { clearMapTrack(); RidingDataModel.startRiding(); } else { if (RidingDataModel.isPaused) { RidingDataModel.resumeRiding(); } else { RidingDataModel.stopRiding(); } } } catch (error) { console.error("操作骑行状态失败:", error); } } onPressAndHold: { if (isDestroyed || !RidingDataModel || !permissionsGranted) return; if (RidingDataModel.isRiding && !RidingDataModel.isPaused) { try { RidingDataModel.pauseRiding(); } catch (error) { console.error("暂停骑行失败:", error); } } } } } } } // 添加错误显示函数 function showMapLoadError(errorMsg) { console.error("地图加载错误:", errorMsg); var errorRect = Qt.createQmlObject(` import QtQuick 2.15 import QtQuick.Controls 2.15 Rectangle { id: errorOverlay anchors.fill: parent color: "#2C3E50" Column { anchors.centerIn: parent spacing: 20 Text { text: "地图加载失败" color: "white" font.pixelSize: 18 font.bold: true } Text { text: "错误信息: ${errorMsg}" color: "#E74C3C" font.pixelSize: 14 width: parent.width * 0.8 wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter } Text { text: "可能的原因:\\n• 网络连接问题\\n• 高德地图API密钥问题\\n• 跨域访问限制" color: "#CCCCCC" font.pixelSize: 12 width: parent.width * 0.8 wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter } Button { text: "重新加载" onClicked: { webView.reload() } } } } `, webView.parent, "errorOverlay"); } } 』」
最新发布
10-29
import QtQuick import QtQuick.Controls // ≥6.3 才带 TreeView import Qt.labs.qmlmodels 6.2 // TreeModel 仍放在 labs import QtQuick.Layouts 1.15 import QtQuick.Window 2.15 import QtQuick.Controls.Basic 6.2 // TreeViewDelegate 所在 Rectangle { id: mainWindow width: 1920 height: 1080 // visible: true // title: "工业备监控系统" // color: "#1e1e2e" color: "transparent" // 摄像机总模型(示例) ListModel { id: cameraModel ListElement { type: "皮带"; name: "Camera1"; cameraId: "CAM-BELT-1" } ListElement { type: "皮带"; name: "Camera2"; cameraId: "CAM-BELT-2" } ListElement { type: "钢丝绳"; name: "Camera3"; cameraId: "CAM-WIRE-1" } ListElement { type: "刮板链"; name: "Camera4"; cameraId: "CAM-CHAIN-1" } ListElement { type: "托辊"; name: "Camera5"; cameraId: "CAM-ROLLER-1" } } // 报表总模型(示例) ListModel { id: reportAllModel ListElement { deviceType:"皮带"; recordId:1; timestamp:"2023-07-15 09:45"; type:"皮带跑偏"; level:"警告" } ListElement { deviceType:"钢丝绳"; recordId:2; timestamp:"2023-07-15 11:30"; type:"断丝"; level:"警告" } ListElement { deviceType:"刮板链"; recordId:3; timestamp:"2023-07-15 13:45"; type:"振动异常"; level:"注意" } ListElement { deviceType:"托辊"; recordId:4; timestamp:"2023-07-15 15:20"; type:"磨损"; level:"严重" } } // 顶部状态栏 Rectangle { id: statusBar y: 0 anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: 0 anchors.rightMargin: 0 height: 0.000001 color: "#00007f" opacity :0 //RowLayout { //anchors.fill: parent //anchors.margins: 10 //spacing: 12 //Text { text: "工业备监控系统"; color: "white"; font.bold: true; font.pixelSize: 18 } //Rectangle { width: 12; height: 12; radius: 6; color: "green" } //Text { text: "系统运行中"; color: "white"; font.pixelSize: 14 } //Item { Layout.fillWidth: true } //Text { // id: timeDisplay // text: Qt.formatDateTime(new Date(), "yyyy-MM-dd hh:mm:ss") // color: "white"; font.pixelSize: 14 // Timer { interval: 1000; running: true; repeat: true; onTriggered: timeDisplay.text = Qt.formatDateTime(new Date(), "yyyy-MM-dd hh:mm:ss") } //} //} } // 主体区域:三列布局(左 列 中 右 列) RowLayout { anchors.top: statusBar.bottom anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right anchors.margins: 12 anchors.leftMargin: 16 anchors.rightMargin: 8 anchors.topMargin: 0 anchors.bottomMargin: 24 spacing: 12 // 左侧:摄像机列表 Rectangle { id: cameraListPanel Layout.preferredWidth: parent.width * 0.2 Layout.fillHeight: true color: "#2c3e50" radius: 8 property string selectedType: "皮带" ColumnLayout { anchors.fill: parent anchors.margins: 12 spacing: 12 Text { text: "摄像机列表"; color: "white"; font.pixelSize: 20; font.bold: true; horizontalAlignment: Text.AlignHCenter } ColumnLayout{ Layout.fillWidth: true // 关键:让内层布局填满外层宽度 spacing: 12 // 建议添加间距,避免组件挤在一起 Rectangle { id: searchBoxContainer Layout.fillWidth: true height: 50 color: "white" border.color: "#ddd" border.width: 1 RowLayout { anchors.fill: parent anchors.leftMargin: 15 anchors.rightMargin: 15 spacing: 10 // 搜索图标 Text { text: "🔍" font.pixelSize: 16 Layout.alignment: Qt.AlignVCenter } // 搜索输入框 TextField { id: searchField Layout.fillWidth: true placeholderText: "输入搜索内容..." font.pixelSize: 16 background: Rectangle { color: "transparent" } onTextChanged: { // 使用计时器延迟搜索,避免频繁搜索 searchTimer.restart() } // 按回车键执行搜索 onAccepted: { performSearch(text) } } } } ComboBox { id: comboBox // 删掉 Layout.preferredWidth: 350,换成自适应 Layout.fillWidth: true // 搜索框一样,填充父容器 Layout.preferredHeight: 50 Layout.preferredWidth: 350 background: Rectangle{ color:"white" } model: ["1 画面", "2 画面", "3 画面", "4 画面"] currentIndex: 0 onCurrentIndexChanged: { cam1.visible = false cam2.visible = false cam3.visible = false cam4.visible = false if (currentIndex >= 0) cam1.visible = true if (currentIndex >= 1) cam2.visible = true if (currentIndex >= 2) cam3.visible = true if (currentIndex >= 3) cam4.visible = true } } } ListModel { id: flatModel ListElement { name: "根节点"; depth: 0; hasChildren: true; visible: true } ListElement { name: "子节点 1"; depth: 1; hasChildren: false; visible: false } ListElement { name: "子节点 2"; depth: 1; hasChildren: false; visible: false } } /* 3. 第一次把顶层灌进去 */ Component.onCompleted: { for (let i = 0; i < rawData.length; i++) flatModel.append({ name: rawData[i].name, depth: 0, expanded: false }); } /* 4. 展开/折叠逻辑 */ function toggle(index) { const node = flatModel.get(index); if (node.depth !== 0) return; // 目前只支持一层,想多层自己递归 if (node.expanded) { // 已展开 → 收拢 /* 从 index+1 开始连续删掉 depth=1 的行 */ for (var i = flatModel.count - 1; i > index; --i) { if (flatModel.get(i).depth === 1) flatModel.remove(i); } flatModel.setProperty(index, "expanded", false); } else { // 未展开 → 展开 const kids = rawData[0].children; // 这里按自己数据结构取 for (let i = 0; i < kids.length; i++) flatModel.insert(index + 1 + i, { name: kids[i], depth: 1 }); flatModel.setProperty(index, "expanded", true); } } TreeView { Layout.preferredWidth: 350 // 关键修复:置最小高度,避免被ListView挤压 Layout.minimumHeight: 250 // 强制保留至少250像素高度 Layout.maximumHeight: 300 // 可选:限制最大高度,防止过高 Layout.alignment: Qt.AlignTop model: flatModel rootIndex: undefined delegate: ItemDelegate { text: model.name width: parent.width leftPadding: model.depth * 20 onClicked: toggle(index) // 这里调用展开/折叠 } } ListView { id: cameraListView Layout.fillWidth: true Layout.fillHeight: true model: ListModel { id: filteredCameraModel } spacing: 6 } } onSelectedTypeChanged: updateCameraModel() function updateCameraModel() { filteredCameraModel.clear() for (let i = 0; i < cameraModel.count; ++i) { const it = cameraModel.get(i) if (it.type === selectedType) filteredCameraModel.append(it) } } } // 中间:4个摄像头(均分 2x2) Rectangle { id: centerPanel Layout.fillWidth: true Layout.fillHeight: true color: "#2c3e50" radius: 8 GridLayout { id: centerGrid anchors.fill: parent anchors.margins: 12 columns: 2 rows: 2 columnSpacing: 12 rowSpacing: 12 // 每个 CameraView 使用 Layout.fillWidth/fillHeight 以均分 CameraView { id: cam1 Layout.fillWidth: true Layout.fillHeight: true title: "摄像头1" cameraId: "CAM-BELT-1" cameraName: "Camera1" borderColor: "#3498db" visible: false // ← 关键 } CameraView { id: cam2 Layout.fillWidth: true Layout.fillHeight: true title: "摄像头2" cameraId: "CAM-WIRE-1" cameraName: "Camera3" borderColor: "#2ecc71" visible: false } CameraView { id: cam3 Layout.fillWidth: true Layout.fillHeight: true title: "摄像头3" cameraId: "CAM-CHAIN-1" cameraName: "Camera4" borderColor: "#e74c3c" visible: false // 👇 关键:当只有3个画面时,cam3 居中显示 Layout.columnSpan: cam3.visible && !cam4.visible ? 2 : 1 Layout.alignment: cam3.visible && !cam4.visible ? Qt.AlignHCenter : Qt.AlignLeft } CameraView { id: cam4 Layout.fillWidth: true Layout.fillHeight: true title: "摄像头4" cameraId: "CAM-ROLLER-1" cameraName: "Camera5" borderColor: "#f39c12" visible: false } } } } Item { id: __materialLibrary__ } /*********************** * CameraView 组件(已修正为用 anchors 控制,确保“模拟画面”在画面区域内部) ***********************/ component CameraView: Rectangle { id: cameraRoot property string title: "摄像机" property string cameraId: "" property string cameraName: "" property color borderColor: "#3498db" color: "#34495e" radius: 8 border.color: borderColor border.width: 3 // 使用 anchors 布局:titleBar 在上方固定高度,videoArea 占剩余空间 // 这样内部的模拟画面始终在 videoArea 区域内居中 Rectangle { id: titleBar anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top height: 36 color: "#2c3e50" radius: 4 border.color: Qt.darker(borderColor, 1.2) border.width: 1 Text { anchors.centerIn: parent text: cameraRoot.title color: "white" font.pixelSize: 16 font.bold: true } } Rectangle { id: videoArea anchors.left: parent.left anchors.right: parent.right anchors.top: titleBar.bottom anchors.bottom: parent.bottom anchors.margins: 6 color: "#2c3e50" radius: 4 border.color: Qt.darker(borderColor, 1.5) border.width: 1 // 背景画面(内边距) Rectangle { id: bg anchors.fill: parent anchors.margins: 6 color: "#1e1e2e" radius: 3 border.color: "#7f8c8d" border.width: 1 } // 中间明显的模拟画面框(**明确锚定到 videoArea 的中心**) Rectangle { id: simVideo width: Math.max(120, videoArea.width * 0.78) height: Math.max(80, videoArea.height * 0.62) anchors.horizontalCenter: videoArea.horizontalCenter anchors.verticalCenter: videoArea.verticalCenter color: "#555555" radius: 6 border.color: "#aaaaaa" border.width: 2 Text { anchors.centerIn: parent text: "模拟画面" color: "white" font.pixelSize: 20 font.bold: true } } // 左下角显示 摄像机名称 + ID(锚定到 videoArea,确保不与 titleBar 重合) Text { id: cameraLabel text: cameraRoot.cameraName + " (" + cameraRoot.cameraId + ")" color: "white" font.pixelSize: 13 anchors.left: videoArea.left anchors.bottom: videoArea.bottom anchors.margins: 8 } // 右上角状态指示器(锚定到 videoArea) Rectangle { width: 12; height: 12; radius: 6 color: "green" anchors.top: videoArea.top anchors.right: videoArea.right anchors.margins: 8 } } } // end CameraView } 为什么树状图没有显示
10-24
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

银狐游戏开发资源2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值