import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Dialogs // import QtQuick.Controls. import "./app.js" as App import "./common" Rectangle { id: root property var appId: App.appId property var plusFile: App.plusFile property var g_user: "" property var g_sn: "8441" property var appPath: "/"+plusFile+"/"+appId+"/" property var firstPath: "" property var logDirectory: "" property var currentLogfilePath: "" property var currentLogResult: "" Component.onCompleted: { firstPath = common.firstPath() console.info("firstPath: " + firstPath) console.info("appPath: " + appPath) // 初始化应用程序脚本,确保脚本已准备就绪 common.initAppScript(appId, "log_processor", "log_processor.py", plusFile) // common.registAppQml(appId, "file_system_model", "FileSystemModel", "file_system_model.py", plusFile) var infos = projectManager.getInfo("all") processInfoDict(infos) g_user = common.getCurrentUser() currentLogfilePath = "" currentLogResult = "" } function processInfoDict(dict) { var curProId = projectManager.getCurrentProId() console.log("curProId: " + curProId) var projects = Object.values(dict) projects.forEach(pro => proFilter.model.push(pro)) var currentIndex = projects.findIndex(pro => pro.id === curProId) if (currentIndex !== -1) { logDirectory = "file:///" + projects[currentIndex].path + "/log" } proFilter.currentIndex = currentIndex } gradient: Gradient { GradientStop { position: 0.0; color: "#ecf3fb" } GradientStop { position: 1.0; color: "#b7d6fb" } orientation: Gradient.Horizontal } ListModel { id: fileListModel } function refreshFileList() { fileListModel.clear() if (logDirectory) { var files = common.getTxtFiles(logDirectory) for (var i = 0; i < files.length; i++) { var fp = files[i].toString() fileListModel.append({ "fileName": fp.substring(fp.lastIndexOf('/') + 1).replace(/\\/g, '/').split('/').pop(), "filePath": fp }) } } } onLogDirectoryChanged: { refreshFileList() } ListModel { id: logModel } property string regexPattern: "" function applyRegexFilter() { // 先拼出所有行(plain + html 两份) var plainLines = [] var htmlLines = [] for (var i = 0; i < logModel.count; i++) { var item = logModel.get(i) var plain = "["+item.time+"] ["+item.level+"] ["+item.tag+"] "+item.message plainLines.push(plain) htmlLines.push(''+plain.replace(/&/g,"&").replace(/') } if (!regexPattern) { logTextArea.text = htmlLines.join("
") return } try { var re = new RegExp(regexPattern, "gim") var fullText = plainLines.join("\n") var resultHtml = [] var match while ((match = re.exec(fullText)) !== null) { // 找到匹配片段,按行拆分后重新着色 var matchedLines = match[0].split("\n") matchedLines.forEach(function(ml) { // 找对应原始行的颜色 var idx = plainLines.indexOf(ml) var color = idx >= 0 ? logModel.get(idx).color : "#000" resultHtml.push(''+ml.replace(/&/g,"&").replace(/') }) } logTextArea.text = resultHtml.join("
") } catch(e) { logTextArea.text = htmlLines.join("
") } } Rectangle { id: bar anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.topMargin: 12 anchors.leftMargin: 23 anchors.rightMargin: 23 height: 45 radius: 9 Row{ anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 12 spacing: 80 Text { text: "日志文件" font.pixelSize: 20 color: "#000000" font.bold: true } } Row { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 15 spacing: 13 Text { text: "选择工程" font.pixelSize: 16 color: "#000000" } ComboBox { id: proFilter width: 150 model: [] textRole: "name" onActivated: { logDirectory = "file:///" + model[currentIndex].path + "/log" logModel.clear() } } ComboBox { id: levelFilter width: 150 model: ["DEBUG", "INFO", "WARNING", "ERROR"] onActivated: { updateLogDisplay() } } ComboBox { id: tagFilter width: 150 model: ["ALL"] onActivated: { updateLogDisplay() } } } } Rectangle { id: fileSystemViewContainer anchors.top: bar.bottom anchors.left: parent.left anchors.bottom: parent.bottom anchors.leftMargin: 23 anchors.bottomMargin: 12 anchors.topMargin: 23 radius: 9 width: 350 clip: true ListView { id: fileSystemView anchors.fill: parent boundsBehavior: Flickable.StopAtBounds ScrollBar.vertical: ScrollBar{} anchors.margins: 5 model: fileListModel delegate: ItemDelegate { text: model.fileName icon.source: "resource/file.svg" background: Rectangle { color: fileSystemView.currentIndex == index ? "#D9D9D9" : "white" } onClicked: { currentLogfilePath = model.filePath currentLogResult = common.readLogFile(currentLogfilePath) updateLogDisplay() fileSystemView.currentIndex = index } } } } function appendTagLogLine(line) { try { if (tagFilter.currentIndex > 0) { if (line.tag.includes(tagFilter.currentText)) { appendLogLine(line) } } else { appendLogLine(line) } } catch(e) { console.error("Error in appendTagLogLine:", e) } } function appendLogLine(line) { try { logModel.append({ "time": line.time, "tag": line.tag, "message": line.msg, "level": line.level, "color": line.color }) } catch(e) { console.error("Error in appendLogLine:", e) } } function updateLogDisplay() { try { logModel.clear() var allTags = new Set() currentLogResult.split("\n").forEach(function(line) { try { var part = line.split("---END")[0] if (part.length > 1) { var item =JSON.parse(part) item.tag.split(",").forEach(tag => allTags.add(tag)) switch (levelFilter.currentIndex) { case 0: appendTagLogLine(item) break; case 1: if(item.level != "DEBUG") { appendTagLogLine(item) } break; case 2: if(item.level === "WARNING" || item.level === "ERROR") { appendTagLogLine(item) } break; case 3: if(item.level === "ERROR") { appendTagLogLine(item) } break; } } } catch(e) { console.error("Error processing log line:", e) } }) tagFilter.model = ["ALL"].concat(Array.from(allTags)) applyRegexFilter() } catch(e) { console.error("Error in updateLogDisplay:", e) } } Rectangle { id: logViewContainer anchors.top: bar.bottom anchors.left: fileSystemViewContainer.right anchors.right: parent.right anchors.bottom: parent.bottom anchors.leftMargin: 23 anchors.bottomMargin: 12 anchors.topMargin: 23 anchors.rightMargin: 23 radius: 9 clip: true // 正则搜索栏 Rectangle { id: searchBar anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.margins: 8 height: 32 radius: 6 color: "#f0f0f0" border.color: regexError ? "red" : "#cccccc" border.width: 1 property bool regexError: false Row { anchors.fill: parent anchors.leftMargin: 8 anchors.rightMargin: 8 spacing: 6 Text { anchors.verticalCenter: parent.verticalCenter text: "正则:" font.pixelSize: 13 color: "#555" } TextInput { id: regexInput anchors.verticalCenter: parent.verticalCenter width: parent.width - 50 font.pixelSize: 13 onTextChanged: { try { if (text) new RegExp(text) searchBar.regexError = false } catch(e) { searchBar.regexError = true } if (!searchBar.regexError) { regexPattern = text applyRegexFilter() } } } } } ScrollView { anchors.top: searchBar.bottom anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom anchors.margins: 10 anchors.topMargin: 6 TextArea { id: logTextArea readOnly: true selectByMouse: true textFormat: TextEdit.RichText wrapMode: TextEdit.NoWrap font.pixelSize: 13 font.family: "Courier New" background: null Keys.onPressed: function(event) { if (event.matches(StandardKey.Copy)) { logTextArea.copy() event.accepted = true } } } } } function updateFileList() { try { if (logDirectory) { var files = common.getTxtFiles(logDirectory) deviceFilter.model = ["全部日志"].concat(files.map(function(file) { return file.substring(file.lastIndexOf('/') + 1); })) return files } return [] } catch(e) { console.error("Error in updateFileList:", e) return [] } } }