import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import JsonEditor 1.0 import HuskarUI.Basic 1.0 ScrollView { width: 600 height: 400 property var projectConfigStr: null // 初始化为空对象 property var saveFunction: function(config){} property var errorLine: null property var errorPosition : null // JSON 处理器 JsonProcessor { id: jsonProcessor } // 将 UTF-8 字节偏移量转换为 UTF-16 代码单元索引 function utf8OffsetToUtf16Index(text, utf8Offset) { var utf16Index = 0; var utf8BytesCount = 0; for (var i = 0; i < text.length; i++) { var charCode = text.charCodeAt(i); var charUtf8Length = 1; if (charCode <= 0x7F) { charUtf8Length = 1; } else if (charCode <= 0x7FF) { charUtf8Length = 2; } else if (charCode <= 0xFFFF) { charUtf8Length = 3; } else { charUtf8Length = 4; } if (utf8BytesCount + charUtf8Length > utf8Offset) { break; } utf8BytesCount += charUtf8Length; utf16Index++; } return utf16Index; } function getErrorLine(text, offset) { var tbuf = text.slice(0,offset); var lines = tbuf.split('\n') if(lines.length > 0)return lines.length return -1 } Rectangle{ id:header_area anchors.fill: parent anchors.bottomMargin: 40 color:"transparent" radius:8 border.color: HusTheme.isDark ? "#23272e" : "#f0f4f7" border.width: 2 ScrollView { id: textScrollView anchors.fill: parent clip: true TextArea { id: jsonEditor font.family: "Consolas" font.pixelSize: 14 wrapMode: TextArea.Wrap placeholderText: "请输入JSON内容..." selectByMouse: true selectionColor: "#0078d4" // 选中区域颜色(Fluent 主题蓝) text:projectConfigStr JsonHighlighter { document: jsonEditor.textDocument } onTextChanged: { var result = jsonProcessor.validateJson(jsonEditor.text) if (result.hasError) { var utf16Index = utf8OffsetToUtf16Index(jsonEditor.text, result.errorOffset); errorPosition = utf16Index // 计算错误行号 errorLine = getErrorLine(jsonEditor.text, utf16Index) errorLabel.text = "❌ JSON语法错误" + `第 ${errorLine} 行出错:${result.errorMessage}` errorCanvas.requestPaint() } else { errorLine = -1 errorPosition = -1 } errorLabel.visible = (errorLine !== -1) } } Canvas { id: errorCanvas anchors.fill: parent visible: errorPosition !== -1 z: 1 renderTarget: Canvas.Image onPaint: { var ctx = getContext("2d") ctx.clearRect(0, 0, width, height) if (errorPosition === -1) return // 获取错误位置在TextArea中的坐标 var rect = jsonEditor.positionToRectangle(errorPosition) if (!rect || rect.width === 0) return // 关键步骤:映射到ScrollView的contentItem(Flickable) var flickablePos = jsonEditor.mapToItem(textScrollView.contentItem, rect.x, rect.y) // 计算滚动偏移后的可视坐标(减去滚动量) var canvasX = flickablePos.x - textScrollView.contentItem.contentX var canvasY = flickablePos.y + textScrollView.contentItem.contentY // 调整标记到文本下方 canvasY += rect.height // 绘制红色三角形(向下箭头) ctx.fillStyle = "#d13438" ctx.beginPath() ctx.moveTo(canvasX, canvasY) // 顶点 ctx.lineTo(canvasX - 8, canvasY + 10) // 左下 ctx.lineTo(canvasX + 8, canvasY + 10) // 右下 ctx.closePath() ctx.fill() } // 滚动时实时更新 Connections { target: textScrollView.contentItem function onContentYChanged(){ errorCanvas.requestPaint() } function onContentXChanged(){ errorCanvas.requestPaint() } } } } } // 按钮区域(底部固定高度) RowLayout { anchors.top:header_area.bottom anchors.topMargin: 6 anchors.horizontalCenter: parent.horizontalCenter spacing: 15 // 错误提示 Text { id: errorLabel Layout.alignment: Qt.AlignHCenter visible: false color: "#d13438" font.bold: true } HusButton { text: "格式化" onClicked: { jsonEditor.text = jsonProcessor.formatJson(jsonEditor.text) } } HusButton { text: "保存" onClicked: saveFunction(jsonEditor.text) } } }