基本完成

This commit is contained in:
2026-05-01 00:10:45 +08:00
parent ebc49d1ac6
commit 826cc4d56e
5 changed files with 140 additions and 74 deletions
+133 -72
View File
@@ -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