181 lines
5.7 KiB
QML
181 lines
5.7 KiB
QML
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)
|
||
}
|
||
}
|
||
}
|