From 826cc4d56e84e448f6a7f88629376ce6e41a4fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=AA=E7=94=B0=E9=87=8E?= Date: Fri, 1 May 2026 00:10:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 + README.md | 3 +- android/gradle.properties | 2 + cmake/rewrite_qt.desktop.in | 2 +- qml/Main.qml | 205 +++++++++++++++++++++++------------- 5 files changed, 140 insertions(+), 74 deletions(-) create mode 100644 android/gradle.properties diff --git a/CMakeLists.txt b/CMakeLists.txt index af3d713..7684448 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.16) project(rewrite_qt VERSION 1.0.0 LANGUAGES CXX) +set(REWRITE_QT_DISPLAY_NAME "人脸识别考勤系统") set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml") set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -54,6 +55,7 @@ endif() if(ANDROID) set_target_properties(apprewrite_qt PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android" + QT_ANDROID_APP_NAME "${REWRITE_QT_DISPLAY_NAME}" QT_ANDROID_APP_ICON "@mipmap/ic_launcher" ) endif() diff --git a/README.md b/README.md index fb05b70..65c162f 100644 --- a/README.md +++ b/README.md @@ -184,10 +184,11 @@ CMake 中 Android 相关配置: ```cmake QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android" +QT_ANDROID_APP_NAME "人脸识别考勤系统" QT_ANDROID_APP_ICON "@mipmap/ic_launcher" ``` -`QT_ANDROID_APP_ICON` 需要 Qt 6.9 或更高版本。当前 Qt 6.10 可正常使用。 +`QT_ANDROID_APP_NAME` 用于设置 Android 桌面显示名,`QT_ANDROID_APP_ICON` 用于设置启动图标。 ## 6. 功能说明 diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..b383ead --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +android.overridePathCheck=true +android.useAndroidX=true diff --git a/cmake/rewrite_qt.desktop.in b/cmake/rewrite_qt.desktop.in index 8c2ec1c..35224cd 100644 --- a/cmake/rewrite_qt.desktop.in +++ b/cmake/rewrite_qt.desktop.in @@ -1,6 +1,6 @@ [Desktop Entry] Type=Application -Name=Face Attendance +Name=人脸识别考勤系统 Exec=apprewrite_qt Icon=rewrite_qt Terminal=false diff --git a/qml/Main.qml b/qml/Main.qml index 86b8a78..7e356d9 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -10,7 +10,7 @@ ApplicationWindow { minimumWidth: 900 minimumHeight: 620 visible: true - title: "人脸识别考勤系统 - QML 管理端" + title: "人脸识别考勤系统" color: "#f6f7f9" readonly property color ink900: "#182230" @@ -43,6 +43,7 @@ ApplicationWindow { readonly property bool denseLayout: width < 1380 readonly property bool narrowLayout: width < 1280 readonly property bool slimLayout: width < 1040 + readonly property bool phoneLayout: width < 560 readonly property int sidebarWidth: slimLayout ? 0 : (compactLayout ? 212 : 240) font.family: "Microsoft YaHei UI" @@ -255,31 +256,33 @@ ApplicationWindow { } component EmptyState: Item { + id: emptyState property string title: "" property string description: "" property string badgeText: "" property color accent: accentColor + readonly property bool tightState: phoneLayout || height < 190 - implicitWidth: 320 - implicitHeight: 220 + implicitWidth: phoneLayout ? 280 : 320 + implicitHeight: phoneLayout ? 154 : 220 + width: Math.min(implicitWidth, parent ? Math.max(180, parent.width - 28) : implicitWidth) + height: Math.min(implicitHeight, parent ? Math.max(126, parent.height - 20) : implicitHeight) z: 20 Rectangle { - anchors.centerIn: parent - width: Math.min(parent.width, 320) - height: 200 + anchors.fill: parent radius: 8 color: "#fffaf4" border.color: "#e7ddd2" Column { anchors.centerIn: parent - spacing: 12 - width: parent.width - 40 + spacing: emptyState.tightState ? 8 : 12 + width: parent.width - 32 Rectangle { - width: 64 - height: 64 + width: emptyState.tightState ? 52 : 64 + height: width radius: 8 color: Qt.tint("#ffffff", Qt.rgba(accent.r, accent.g, accent.b, 0.16)) anchors.horizontalCenter: parent.horizontalCenter @@ -288,7 +291,7 @@ ApplicationWindow { anchors.centerIn: parent text: badgeText color: accent - font.pixelSize: 22 + font.pixelSize: emptyState.tightState ? 18 : 22 font.bold: true } } @@ -299,17 +302,18 @@ ApplicationWindow { horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap color: ink900 - font.pixelSize: 20 + font.pixelSize: emptyState.tightState ? 18 : 20 font.bold: true } Label { width: parent.width + visible: !emptyState.tightState || parent.width > 260 text: description horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap color: ink500 - font.pixelSize: 13 + font.pixelSize: emptyState.tightState ? 12 : 13 } } } @@ -341,7 +345,7 @@ ApplicationWindow { property string description: "" property color accent: accentColor - implicitHeight: descriptionLabel.visible ? 52 : 32 + implicitHeight: descriptionLabel.visible ? (phoneLayout ? 64 : 52) : 32 implicitWidth: 300 RowLayout { @@ -362,7 +366,7 @@ ApplicationWindow { Label { Layout.fillWidth: true text: sectionHeader.title - font.pixelSize: 22 + font.pixelSize: phoneLayout ? 20 : 22 font.bold: true color: ink900 wrapMode: Text.WordWrap @@ -404,6 +408,29 @@ ApplicationWindow { } } + component MobileNavTabButton: TabButton { + id: control + implicitHeight: 42 + padding: 0 + + background: Rectangle { + radius: 8 + color: control.checked ? tealColor : panelStrong + border.width: 1 + border.color: control.checked ? tealColor : lineColor + } + + contentItem: Label { + text: control.text + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: control.checked ? "#f6fbfb" : ink700 + font.pixelSize: phoneLayout ? 12 : 13 + font.bold: true + elide: Text.ElideRight + } + } + function applyRecordFilters() { appController.applyFilters( classFilter.currentText, @@ -702,8 +729,11 @@ ApplicationWindow { Label { anchors.centerIn: parent - text: "管理端" - font.pixelSize: compactLayout ? 24 : 28 + width: parent.width - 18 + text: "人脸识别考勤系统" + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + font.pixelSize: compactLayout ? 17 : 19 font.bold: true color: "#ffffff" } @@ -835,7 +865,7 @@ ApplicationWindow { Rectangle { Layout.fillWidth: true - Layout.preferredHeight: slimLayout ? 96 : 142 + Layout.preferredHeight: phoneLayout ? 108 : (slimLayout ? 96 : 142) radius: 8 color: panelStrong border.color: lineColor @@ -852,10 +882,12 @@ ApplicationWindow { spacing: 6 Label { + Layout.fillWidth: true text: ["考勤记录总览", "统计分析面板", "配置与同步中心", "人脸资料管理", "关于"][window.currentPageIndex] font.pixelSize: slimLayout ? 20 : (compactLayout ? 24 : 28) font.bold: true color: ink900 + elide: Text.ElideRight } Label { @@ -922,15 +954,19 @@ ApplicationWindow { id: navigationBar visible: slimLayout Layout.fillWidth: true - Layout.preferredHeight: visible ? implicitHeight : 0 + Layout.preferredHeight: visible ? 42 : 0 + spacing: 6 + padding: 0 + property real tabWidth: (width - spacing * 4) / 5 currentIndex: window.currentPageIndex onCurrentIndexChanged: window.switchPage(currentIndex) + background: Item {} - TabButton { text: "考勤记录" } - TabButton { text: "统计分析" } - TabButton { text: "配置中心" } - TabButton { text: "人脸管理" } - TabButton { text: "关于" } + MobileNavTabButton { width: navigationBar.tabWidth; text: "考勤记录" } + MobileNavTabButton { width: navigationBar.tabWidth; text: "统计分析" } + MobileNavTabButton { width: navigationBar.tabWidth; text: "配置中心" } + MobileNavTabButton { width: navigationBar.tabWidth; text: "人脸管理" } + MobileNavTabButton { width: navigationBar.tabWidth; text: "关于" } } StackLayout { @@ -1069,7 +1105,7 @@ ApplicationWindow { radius: 8 color: panelStrong border.color: lineColor - Layout.preferredHeight: slimLayout ? 174 : 92 + Layout.preferredHeight: phoneLayout ? 236 : (slimLayout ? 174 : 92) clip: true GridLayout { @@ -1080,8 +1116,8 @@ ApplicationWindow { rowSpacing: 12 Column { - Layout.preferredWidth: 150 - width: 150 + Layout.fillWidth: true + Layout.preferredWidth: phoneLayout ? 0 : 150 spacing: 6 Label { text: "班级"; color: ink700; font.bold: true; font.pixelSize: 12 } AppComboBox { @@ -1093,8 +1129,8 @@ ApplicationWindow { } Column { - Layout.preferredWidth: 160 - width: 160 + Layout.fillWidth: true + Layout.preferredWidth: phoneLayout ? 0 : 160 spacing: 6 Label { text: "学号"; color: ink700; font.bold: true; font.pixelSize: 12 } AppComboBox { @@ -1106,8 +1142,8 @@ ApplicationWindow { } Column { - Layout.preferredWidth: 190 - width: 190 + Layout.fillWidth: true + Layout.preferredWidth: phoneLayout ? 0 : 190 spacing: 6 Label { text: "姓名"; color: ink700; font.bold: true; font.pixelSize: 12 } AppTextField { @@ -1119,8 +1155,8 @@ ApplicationWindow { } Column { - Layout.preferredWidth: 160 - width: 160 + Layout.fillWidth: true + Layout.preferredWidth: phoneLayout ? 0 : 160 spacing: 6 Label { text: "日期"; color: ink700; font.bold: true; font.pixelSize: 12 } AppComboBox { @@ -1133,8 +1169,9 @@ ApplicationWindow { } Column { - Layout.preferredWidth: 150 - width: dateFilter.currentIndex === 4 ? 150 : 0 + Layout.fillWidth: true + Layout.preferredWidth: phoneLayout ? 0 : 150 + width: dateFilter.currentIndex === 4 ? implicitWidth : 0 height: dateFilter.currentIndex === 4 ? implicitHeight : 0 visible: dateFilter.currentIndex === 4 spacing: 6 @@ -1148,9 +1185,11 @@ ApplicationWindow { } } - Row { + Flow { + Layout.columnSpan: slimLayout ? 2 : 1 + Layout.fillWidth: true Layout.alignment: Qt.AlignBottom - height: 42 + height: implicitHeight spacing: 8 SecondaryButton { @@ -1180,12 +1219,12 @@ ApplicationWindow { radius: 8 color: panelStrong border.color: lineColor - property real timeColWidth: Math.max(168, Math.min(220, width * 0.21)) - property real classColWidth: Math.max(88, Math.min(124, width * 0.11)) - property real studentColWidth: Math.max(86, Math.min(116, width * 0.10)) - property real nameColWidth: Math.max(118, Math.min(158, width * 0.14)) - property real confidenceColWidth: Math.max(96, Math.min(126, width * 0.11)) - property real statusColWidth: Math.max(84, Math.min(102, width * 0.09)) + property real timeColWidth: phoneLayout ? 112 : Math.max(168, Math.min(220, width * 0.21)) + property real classColWidth: phoneLayout ? 0 : Math.max(88, Math.min(124, width * 0.11)) + property real studentColWidth: phoneLayout ? 64 : Math.max(86, Math.min(116, width * 0.10)) + property real nameColWidth: phoneLayout ? 88 : Math.max(118, Math.min(158, width * 0.14)) + property real confidenceColWidth: phoneLayout ? 0 : Math.max(96, Math.min(126, width * 0.11)) + property real statusColWidth: phoneLayout ? 64 : Math.max(84, Math.min(102, width * 0.09)) property bool showClassColumn: !denseLayout property bool showConfidenceColumn: !narrowLayout @@ -1325,7 +1364,7 @@ ApplicationWindow { radius: 8 color: panelStrong border.color: lineColor - Layout.preferredHeight: slimLayout ? 150 : 78 + Layout.preferredHeight: phoneLayout ? 186 : (slimLayout ? 150 : 78) clip: true GridLayout { @@ -1338,7 +1377,8 @@ ApplicationWindow { AppComboBox { id: quickStatisticsFilter - Layout.preferredWidth: 150 + Layout.fillWidth: true + Layout.preferredWidth: phoneLayout ? 0 : 150 model: ["今天", "最近7天", "最近30天", "本月"] currentIndex: 2 onCurrentTextChanged: { @@ -1349,14 +1389,16 @@ ApplicationWindow { AppTextField { id: statsStartField - Layout.preferredWidth: 150 + Layout.fillWidth: true + Layout.preferredWidth: phoneLayout ? 0 : 150 placeholderText: "开始日期" onEditingFinished: appController.updateStatisticsRange(text, statsEndField.text) } AppTextField { id: statsEndField - Layout.preferredWidth: 150 + Layout.fillWidth: true + Layout.preferredWidth: phoneLayout ? 0 : 150 placeholderText: "结束日期" onEditingFinished: appController.updateStatisticsRange(statsStartField.text, text) } @@ -1375,13 +1417,13 @@ ApplicationWindow { GridLayout { Layout.fillWidth: true - columns: slimLayout ? 3 : (compactLayout ? 1 : 3) + columns: phoneLayout ? 1 : (slimLayout ? 3 : (compactLayout ? 1 : 3)) rowSpacing: 10 columnSpacing: 14 Rectangle { Layout.fillWidth: true - Layout.preferredHeight: slimLayout ? 74 : 100 + Layout.preferredHeight: phoneLayout ? 68 : (slimLayout ? 74 : 100) radius: 8 color: panelStrong border.color: lineColor @@ -1411,7 +1453,7 @@ ApplicationWindow { Rectangle { Layout.fillWidth: true - Layout.preferredHeight: slimLayout ? 74 : 100 + Layout.preferredHeight: phoneLayout ? 68 : (slimLayout ? 74 : 100) radius: 8 color: panelStrong border.color: lineColor @@ -1441,7 +1483,7 @@ ApplicationWindow { Rectangle { Layout.fillWidth: true - Layout.preferredHeight: slimLayout ? 74 : 100 + Layout.preferredHeight: phoneLayout ? 68 : (slimLayout ? 74 : 100) radius: 8 color: panelStrong border.color: lineColor @@ -1460,6 +1502,7 @@ ApplicationWindow { font.pixelSize: slimLayout ? 14 : (compactLayout ? 18 : 20) font.bold: true color: goldColor + elide: Text.ElideRight } } @@ -1483,14 +1526,15 @@ ApplicationWindow { radius: 8 color: panelStrong border.color: lineColor - property real studentColWidth: Math.max(90, Math.min(128, width * 0.12)) - property real nameColWidth: Math.max(128, Math.min(188, width * 0.17)) - property real totalColWidth: Math.max(96, Math.min(124, width * 0.12)) + property real studentColWidth: phoneLayout ? 68 : Math.max(90, Math.min(128, width * 0.12)) + property real nameColWidth: phoneLayout ? 96 : Math.max(128, Math.min(188, width * 0.17)) + property real totalColWidth: phoneLayout ? 92 : Math.max(96, Math.min(124, width * 0.12)) property real todayColWidth: Math.max(92, Math.min(110, width * 0.10)) property real lastColWidth: Math.max(180, Math.min(240, width * 0.20)) property real avgColWidth: Math.max(98, Math.min(126, width * 0.11)) property bool showTodayColumn: !denseLayout property bool showLastColumn: !narrowLayout + property bool showAverageColumn: !phoneLayout ColumnLayout { anchors.fill: parent @@ -1524,7 +1568,12 @@ ApplicationWindow { Layout.preferredWidth: statisticsTableCard.lastColWidth font.bold: true } - Label { text: "平均置信度"; Layout.preferredWidth: statisticsTableCard.avgColWidth; font.bold: true } + Label { + text: "平均置信度" + visible: statisticsTableCard.showAverageColumn + Layout.preferredWidth: statisticsTableCard.avgColWidth + font.bold: true + } Item { Layout.fillWidth: true } } } @@ -1574,7 +1623,13 @@ ApplicationWindow { Layout.preferredWidth: statisticsTableCard.lastColWidth color: ink700 } - Label { text: model.averageConfidenceText; Layout.preferredWidth: statisticsTableCard.avgColWidth; color: tealColor; font.bold: true } + Label { + text: model.averageConfidenceText + visible: statisticsTableCard.showAverageColumn + Layout.preferredWidth: statisticsTableCard.avgColWidth + color: tealColor + font.bold: true + } Item { Layout.fillWidth: true } } } @@ -1622,7 +1677,7 @@ ApplicationWindow { radius: 8 color: panelStrong border.color: lineColor - Layout.preferredHeight: slimLayout ? 174 : 88 + Layout.preferredHeight: phoneLayout ? 218 : (slimLayout ? 174 : 88) clip: true GridLayout { @@ -1648,11 +1703,13 @@ ApplicationWindow { } SecondaryButton { + Layout.fillWidth: phoneLayout text: "保存地址" onClicked: appController.serverUrl = serverUrlField.text } PrimaryButton { + Layout.fillWidth: phoneLayout text: "测试连接" fillColor: tealColor onClicked: appController.testConnection() @@ -2019,13 +2076,15 @@ ApplicationWindow { radius: 8 color: panelStrong border.color: lineColor - Layout.preferredHeight: slimLayout ? 132 : 88 + Layout.preferredHeight: phoneLayout ? 170 : (slimLayout ? 132 : 88) clip: true - RowLayout { + GridLayout { anchors.fill: parent anchors.margins: slimLayout ? 12 : 18 - spacing: 12 + columns: phoneLayout ? 1 : 2 + rowSpacing: 12 + columnSpacing: 12 SectionHeader { Layout.fillWidth: true @@ -2034,8 +2093,9 @@ ApplicationWindow { accent: goldColor } - Row { - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + Flow { + Layout.fillWidth: phoneLayout + Layout.alignment: phoneLayout ? Qt.AlignLeft : (Qt.AlignRight | Qt.AlignVCenter) spacing: 8 SecondaryButton { text: "刷新"; onClicked: appController.refreshFaces() } @@ -2059,14 +2119,14 @@ ApplicationWindow { anchors.fill: parent anchors.margins: 16 clip: true - property int cardColumns: Math.max(1, Math.floor(width / (compactLayout ? 236 : 260))) - cellWidth: Math.max(240, Math.floor(width / cardColumns)) - cellHeight: 332 + property int cardColumns: phoneLayout ? 1 : Math.max(1, Math.floor(width / (compactLayout ? 236 : 260))) + cellWidth: Math.max(phoneLayout ? 280 : 240, Math.floor(width / cardColumns)) + cellHeight: phoneLayout ? 392 : 352 model: appController.faceModel delegate: Rectangle { - width: Math.min(244, GridView.view.cellWidth - 18) - height: 308 + width: Math.min(phoneLayout ? 300 : 244, GridView.view.cellWidth - 18) + height: phoneLayout ? 368 : 328 property bool hovered: faceHoverHandler.hovered radius: 8 color: hovered ? "#fff6ef" : "#ffffff" @@ -2085,7 +2145,7 @@ ApplicationWindow { Rectangle { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - Layout.preferredHeight: compactLayout ? 152 : 168 + Layout.preferredHeight: phoneLayout ? 140 : (compactLayout ? 152 : 168) radius: 16 color: model.hasPhoto ? "#eef3f5" : goldSoft border.color: model.hasPhoto ? "#dbe4ea" : "#eadbb0" @@ -2179,15 +2239,16 @@ ApplicationWindow { radius: 8 color: panelStrong border.color: lineColor + clip: true ColumnLayout { anchors.fill: parent - anchors.margins: 24 - spacing: 18 + anchors.margins: phoneLayout ? 16 : 24 + spacing: phoneLayout ? 12 : 18 SectionHeader { Layout.fillWidth: true - title: "人脸识别考勤系统管理端" + title: "人脸识别考勤系统" description: "用于连接考勤服务端,集中查看签到记录、统计考勤情况、维护签到时段、管理名称映射与人脸照片绑定。" accent: tealColor } @@ -2220,7 +2281,7 @@ ApplicationWindow { Rectangle { Layout.fillWidth: true - Layout.preferredHeight: 92 + Layout.preferredHeight: phoneLayout ? 124 : 108 radius: 8 color: "#f8fafb" border.color: lineColor