完善移动端布局与发布配置
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Jitianye
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -421,3 +421,7 @@ X-Encrypted-Response: true
|
|||||||
- 如果服务端加密参数变化,需要同步修改 `src/CryptoUtils.cpp`。
|
- 如果服务端加密参数变化,需要同步修改 `src/CryptoUtils.cpp`。
|
||||||
- 如果要发布桌面版本,建议使用 Qt 部署工具收集运行时依赖。
|
- 如果要发布桌面版本,建议使用 Qt 部署工具收集运行时依赖。
|
||||||
- 如果要发布 Android 版本,建议补充签名配置和 release 构建流程。
|
- 如果要发布 Android 版本,建议补充签名配置和 release 构建流程。
|
||||||
|
|
||||||
|
## 13. 开源协议
|
||||||
|
|
||||||
|
本项目采用 MIT License 开源,详见 `LICENSE` 文件。
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QQuickStyle>
|
#include <QQuickStyle>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
|
||||||
#include "src/AppController.h"
|
#include "src/AppController.h"
|
||||||
|
|
||||||
@@ -24,6 +25,10 @@ int main(int argc, char *argv[])
|
|||||||
QCoreApplication::setOrganizationName("STM32 Face Recognition");
|
QCoreApplication::setOrganizationName("STM32 Face Recognition");
|
||||||
QQuickStyle::setStyle("Basic");
|
QQuickStyle::setStyle("Basic");
|
||||||
|
|
||||||
|
#if defined(Q_OS_ANDROID)
|
||||||
|
QQuickWindow::setTextRenderType(QQuickWindow::NativeTextRendering);
|
||||||
|
#endif
|
||||||
|
|
||||||
AppController controller;
|
AppController controller;
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
|
|||||||
+102
-85
@@ -29,6 +29,8 @@ ApplicationWindow {
|
|||||||
readonly property color goldSoft: "#f5e8c6"
|
readonly property color goldSoft: "#f5e8c6"
|
||||||
readonly property color successColor: "#1f7a4d"
|
readonly property color successColor: "#1f7a4d"
|
||||||
readonly property color dangerColor: "#b9382f"
|
readonly property color dangerColor: "#b9382f"
|
||||||
|
readonly property string appFontFamily: Qt.platform.os === "android" ? "sans-serif" : "Microsoft YaHei UI"
|
||||||
|
readonly property int appBoldWeight: Qt.platform.os === "android" ? Font.Medium : Font.Bold
|
||||||
|
|
||||||
property string statisticsPreset: "最近30天"
|
property string statisticsPreset: "最近30天"
|
||||||
property int editingTimeSlotIndex: -1
|
property int editingTimeSlotIndex: -1
|
||||||
@@ -46,7 +48,7 @@ ApplicationWindow {
|
|||||||
readonly property bool phoneLayout: width < 560
|
readonly property bool phoneLayout: width < 560
|
||||||
readonly property int sidebarWidth: slimLayout ? 0 : (compactLayout ? 212 : 240)
|
readonly property int sidebarWidth: slimLayout ? 0 : (compactLayout ? 212 : 240)
|
||||||
|
|
||||||
font.family: "Microsoft YaHei UI"
|
font.family: appFontFamily
|
||||||
|
|
||||||
onWidthChanged: {
|
onWidthChanged: {
|
||||||
resizingWindow = true
|
resizingWindow = true
|
||||||
@@ -174,7 +176,7 @@ ApplicationWindow {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
color: control.labelColor
|
color: control.labelColor
|
||||||
font.pixelSize: 13
|
font.pixelSize: 13
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +203,7 @@ ApplicationWindow {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
color: control.labelColor
|
color: control.labelColor
|
||||||
font.pixelSize: 13
|
font.pixelSize: 13
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +294,7 @@ ApplicationWindow {
|
|||||||
text: badgeText
|
text: badgeText
|
||||||
color: accent
|
color: accent
|
||||||
font.pixelSize: emptyState.tightState ? 18 : 22
|
font.pixelSize: emptyState.tightState ? 18 : 22
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +305,7 @@ ApplicationWindow {
|
|||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
color: ink900
|
color: ink900
|
||||||
font.pixelSize: emptyState.tightState ? 18 : 20
|
font.pixelSize: emptyState.tightState ? 18 : 20
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -335,7 +337,7 @@ ApplicationWindow {
|
|||||||
text: parent.textValue
|
text: parent.textValue
|
||||||
color: parent.tintColor
|
color: parent.tintColor
|
||||||
font.pixelSize: 11
|
font.pixelSize: 11
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +369,7 @@ ApplicationWindow {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: sectionHeader.title
|
text: sectionHeader.title
|
||||||
font.pixelSize: phoneLayout ? 20 : 22
|
font.pixelSize: phoneLayout ? 20 : 22
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
color: ink900
|
color: ink900
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
}
|
}
|
||||||
@@ -403,7 +405,7 @@ ApplicationWindow {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
color: control.checked ? "#f6fbfb" : ink700
|
color: control.checked ? "#f6fbfb" : ink700
|
||||||
font.pixelSize: 13
|
font.pixelSize: 13
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -426,7 +428,7 @@ ApplicationWindow {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
color: control.checked ? "#f6fbfb" : ink700
|
color: control.checked ? "#f6fbfb" : ink700
|
||||||
font.pixelSize: phoneLayout ? 12 : 13
|
font.pixelSize: phoneLayout ? 12 : 13
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -576,7 +578,7 @@ ApplicationWindow {
|
|||||||
Label {
|
Label {
|
||||||
text: "确定要清空服务器上的所有签到记录吗?"
|
text: "确定要清空服务器上的所有签到记录吗?"
|
||||||
font.pixelSize: 20
|
font.pixelSize: 20
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
color: ink900
|
color: ink900
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,7 +684,7 @@ ApplicationWindow {
|
|||||||
text: "确定删除 ID 为 " + pendingDeleteFaceId + " 的人脸信息吗?"
|
text: "确定删除 ID 为 " + pendingDeleteFaceId + " 的人脸信息吗?"
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
font.pixelSize: 18
|
font.pixelSize: 18
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
color: ink900
|
color: ink900
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,7 +736,7 @@ ApplicationWindow {
|
|||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
font.pixelSize: compactLayout ? 17 : 19
|
font.pixelSize: compactLayout ? 17 : 19
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
color: "#ffffff"
|
color: "#ffffff"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -801,7 +803,7 @@ ApplicationWindow {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: modelData.icon
|
text: modelData.icon
|
||||||
color: navButton.selected ? tealColor : "#d6e4e9"
|
color: navButton.selected ? tealColor : "#d6e4e9"
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -814,7 +816,7 @@ ApplicationWindow {
|
|||||||
text: modelData.title
|
text: modelData.title
|
||||||
color: navButton.selected ? ink900 : "#f4fbff"
|
color: navButton.selected ? ink900 : "#f4fbff"
|
||||||
font.pixelSize: compactLayout ? 14 : 15
|
font.pixelSize: compactLayout ? 14 : 15
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -885,7 +887,7 @@ ApplicationWindow {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: ["考勤记录总览", "统计分析面板", "配置与同步中心", "人脸资料管理", "关于"][window.currentPageIndex]
|
text: ["考勤记录总览", "统计分析面板", "配置与同步中心", "人脸资料管理", "关于"][window.currentPageIndex]
|
||||||
font.pixelSize: slimLayout ? 20 : (compactLayout ? 24 : 28)
|
font.pixelSize: slimLayout ? 20 : (compactLayout ? 24 : 28)
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
color: ink900
|
color: ink900
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
@@ -1013,7 +1015,7 @@ ApplicationWindow {
|
|||||||
Label {
|
Label {
|
||||||
text: appController.totalRecords
|
text: appController.totalRecords
|
||||||
font.pixelSize: slimLayout ? 22 : (compactLayout ? 30 : 34)
|
font.pixelSize: slimLayout ? 22 : (compactLayout ? 30 : 34)
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
color: ink900
|
color: ink900
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1048,7 +1050,7 @@ ApplicationWindow {
|
|||||||
Label {
|
Label {
|
||||||
text: appController.todayRecords
|
text: appController.todayRecords
|
||||||
font.pixelSize: slimLayout ? 22 : (compactLayout ? 30 : 34)
|
font.pixelSize: slimLayout ? 22 : (compactLayout ? 30 : 34)
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
color: tealColor
|
color: tealColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1083,7 +1085,7 @@ ApplicationWindow {
|
|||||||
Label {
|
Label {
|
||||||
text: appController.filteredCount + " / " + appController.loadedRecords
|
text: appController.filteredCount + " / " + appController.loadedRecords
|
||||||
font.pixelSize: slimLayout ? 20 : (compactLayout ? 26 : 30)
|
font.pixelSize: slimLayout ? 20 : (compactLayout ? 26 : 30)
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
color: goldColor
|
color: goldColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1117,9 +1119,9 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: phoneLayout ? 0 : 150
|
Layout.preferredWidth: phoneLayout ? 1 : 150
|
||||||
spacing: 6
|
spacing: 6
|
||||||
Label { text: "班级"; color: ink700; font.bold: true; font.pixelSize: 12 }
|
Label { text: "班级"; color: ink700; font.weight: appBoldWeight; font.pixelSize: 12 }
|
||||||
AppComboBox {
|
AppComboBox {
|
||||||
id: classFilter
|
id: classFilter
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -1130,9 +1132,9 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: phoneLayout ? 0 : 160
|
Layout.preferredWidth: phoneLayout ? 1 : 160
|
||||||
spacing: 6
|
spacing: 6
|
||||||
Label { text: "学号"; color: ink700; font.bold: true; font.pixelSize: 12 }
|
Label { text: "学号"; color: ink700; font.weight: appBoldWeight; font.pixelSize: 12 }
|
||||||
AppComboBox {
|
AppComboBox {
|
||||||
id: studentFilter
|
id: studentFilter
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -1143,9 +1145,9 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: phoneLayout ? 0 : 190
|
Layout.preferredWidth: phoneLayout ? 1 : 190
|
||||||
spacing: 6
|
spacing: 6
|
||||||
Label { text: "姓名"; color: ink700; font.bold: true; font.pixelSize: 12 }
|
Label { text: "姓名"; color: ink700; font.weight: appBoldWeight; font.pixelSize: 12 }
|
||||||
AppTextField {
|
AppTextField {
|
||||||
id: nameFilter
|
id: nameFilter
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -1156,9 +1158,9 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: phoneLayout ? 0 : 160
|
Layout.preferredWidth: phoneLayout ? 1 : 160
|
||||||
spacing: 6
|
spacing: 6
|
||||||
Label { text: "日期"; color: ink700; font.bold: true; font.pixelSize: 12 }
|
Label { text: "日期"; color: ink700; font.weight: appBoldWeight; font.pixelSize: 12 }
|
||||||
AppComboBox {
|
AppComboBox {
|
||||||
id: dateFilter
|
id: dateFilter
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -1170,12 +1172,12 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: phoneLayout ? 0 : 150
|
Layout.preferredWidth: phoneLayout ? 1 : 150
|
||||||
width: dateFilter.currentIndex === 4 ? implicitWidth : 0
|
width: dateFilter.currentIndex === 4 ? implicitWidth : 0
|
||||||
height: dateFilter.currentIndex === 4 ? implicitHeight : 0
|
height: dateFilter.currentIndex === 4 ? implicitHeight : 0
|
||||||
visible: dateFilter.currentIndex === 4
|
visible: dateFilter.currentIndex === 4
|
||||||
spacing: 6
|
spacing: 6
|
||||||
Label { text: "指定日期"; color: ink700; font.bold: true; font.pixelSize: 12 }
|
Label { text: "指定日期"; color: ink700; font.weight: appBoldWeight; font.pixelSize: 12 }
|
||||||
AppTextField {
|
AppTextField {
|
||||||
id: specificDateField
|
id: specificDateField
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -1185,29 +1187,28 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Flow {
|
GridLayout {
|
||||||
Layout.columnSpan: slimLayout ? 2 : 1
|
Layout.columnSpan: slimLayout ? 2 : 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignBottom
|
Layout.alignment: Qt.AlignBottom
|
||||||
height: implicitHeight
|
columns: phoneLayout ? 2 : 2
|
||||||
spacing: 8
|
columnSpacing: 8
|
||||||
|
rowSpacing: 10
|
||||||
|
|
||||||
SecondaryButton {
|
SecondaryButton {
|
||||||
text: "刷新"
|
Layout.fillWidth: true
|
||||||
onClicked: appController.refreshRecords()
|
Layout.preferredWidth: 1
|
||||||
}
|
text: "刷新"
|
||||||
|
onClicked: appController.refreshRecords()
|
||||||
|
}
|
||||||
|
|
||||||
PrimaryButton {
|
DangerButton {
|
||||||
text: "导出"
|
Layout.fillWidth: true
|
||||||
fillColor: tealColor
|
Layout.preferredWidth: 1
|
||||||
onClicked: recordExportDialog.open()
|
text: "清空记录"
|
||||||
|
onClicked: clearDialog.open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DangerButton {
|
|
||||||
text: "清空记录"
|
|
||||||
onClicked: clearDialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1245,24 +1246,24 @@ ApplicationWindow {
|
|||||||
anchors.rightMargin: 16
|
anchors.rightMargin: 16
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
Label { text: "签到时间"; Layout.preferredWidth: recordTableCard.timeColWidth; font.bold: true; color: ink900 }
|
Label { text: "签到时间"; Layout.preferredWidth: recordTableCard.timeColWidth; font.weight: appBoldWeight; color: ink900 }
|
||||||
Label {
|
Label {
|
||||||
text: "班级"
|
text: "班级"
|
||||||
visible: recordTableCard.showClassColumn
|
visible: recordTableCard.showClassColumn
|
||||||
Layout.preferredWidth: recordTableCard.classColWidth
|
Layout.preferredWidth: recordTableCard.classColWidth
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
color: ink900
|
color: ink900
|
||||||
}
|
}
|
||||||
Label { text: "学号"; Layout.preferredWidth: recordTableCard.studentColWidth; font.bold: true; color: ink900 }
|
Label { text: "学号"; Layout.preferredWidth: recordTableCard.studentColWidth; font.weight: appBoldWeight; color: ink900 }
|
||||||
Label { text: "姓名"; Layout.preferredWidth: recordTableCard.nameColWidth; font.bold: true; color: ink900 }
|
Label { text: "姓名"; Layout.preferredWidth: recordTableCard.nameColWidth; font.weight: appBoldWeight; color: ink900 }
|
||||||
Label {
|
Label {
|
||||||
text: "置信度"
|
text: "置信度"
|
||||||
visible: recordTableCard.showConfidenceColumn
|
visible: recordTableCard.showConfidenceColumn
|
||||||
Layout.preferredWidth: recordTableCard.confidenceColWidth
|
Layout.preferredWidth: recordTableCard.confidenceColWidth
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
color: ink900
|
color: ink900
|
||||||
}
|
}
|
||||||
Label { text: "状态"; Layout.preferredWidth: recordTableCard.statusColWidth; font.bold: true; color: ink900 }
|
Label { text: "状态"; Layout.preferredWidth: recordTableCard.statusColWidth; font.weight: appBoldWeight; color: ink900 }
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1305,7 +1306,7 @@ ApplicationWindow {
|
|||||||
color: ink700
|
color: ink700
|
||||||
}
|
}
|
||||||
Label { text: model.studentId; Layout.preferredWidth: recordTableCard.studentColWidth; color: ink700; elide: Text.ElideRight }
|
Label { text: model.studentId; Layout.preferredWidth: recordTableCard.studentColWidth; color: ink700; elide: Text.ElideRight }
|
||||||
Label { text: model.name; Layout.preferredWidth: recordTableCard.nameColWidth; color: ink900; font.bold: true; elide: Text.ElideRight }
|
Label { text: model.name; Layout.preferredWidth: recordTableCard.nameColWidth; color: ink900; font.weight: appBoldWeight; elide: Text.ElideRight }
|
||||||
Label {
|
Label {
|
||||||
text: model.confidenceText
|
text: model.confidenceText
|
||||||
visible: recordTableCard.showConfidenceColumn
|
visible: recordTableCard.showConfidenceColumn
|
||||||
@@ -1322,7 +1323,7 @@ ApplicationWindow {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: model.statusText
|
text: model.statusText
|
||||||
color: model.success ? successColor : dangerColor
|
color: model.success ? successColor : dangerColor
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
@@ -1364,7 +1365,7 @@ ApplicationWindow {
|
|||||||
radius: 8
|
radius: 8
|
||||||
color: panelStrong
|
color: panelStrong
|
||||||
border.color: lineColor
|
border.color: lineColor
|
||||||
Layout.preferredHeight: phoneLayout ? 186 : (slimLayout ? 150 : 78)
|
Layout.preferredHeight: phoneLayout ? 150 : (slimLayout ? 150 : 78)
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
GridLayout {
|
GridLayout {
|
||||||
@@ -1378,7 +1379,7 @@ ApplicationWindow {
|
|||||||
AppComboBox {
|
AppComboBox {
|
||||||
id: quickStatisticsFilter
|
id: quickStatisticsFilter
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: phoneLayout ? 0 : 150
|
Layout.preferredWidth: phoneLayout ? 1 : 150
|
||||||
model: ["今天", "最近7天", "最近30天", "本月"]
|
model: ["今天", "最近7天", "最近30天", "本月"]
|
||||||
currentIndex: 2
|
currentIndex: 2
|
||||||
onCurrentTextChanged: {
|
onCurrentTextChanged: {
|
||||||
@@ -1390,7 +1391,7 @@ ApplicationWindow {
|
|||||||
AppTextField {
|
AppTextField {
|
||||||
id: statsStartField
|
id: statsStartField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: phoneLayout ? 0 : 150
|
Layout.preferredWidth: phoneLayout ? 1 : 150
|
||||||
placeholderText: "开始日期"
|
placeholderText: "开始日期"
|
||||||
onEditingFinished: appController.updateStatisticsRange(text, statsEndField.text)
|
onEditingFinished: appController.updateStatisticsRange(text, statsEndField.text)
|
||||||
}
|
}
|
||||||
@@ -1398,31 +1399,29 @@ ApplicationWindow {
|
|||||||
AppTextField {
|
AppTextField {
|
||||||
id: statsEndField
|
id: statsEndField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: phoneLayout ? 0 : 150
|
Layout.preferredWidth: phoneLayout ? 1 : 150
|
||||||
placeholderText: "结束日期"
|
placeholderText: "结束日期"
|
||||||
onEditingFinished: appController.updateStatisticsRange(statsStartField.text, text)
|
onEditingFinished: appController.updateStatisticsRange(statsStartField.text, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
SecondaryButton {
|
SecondaryButton {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredWidth: phoneLayout ? 1 : implicitWidth
|
||||||
text: "刷新统计"
|
text: "刷新统计"
|
||||||
onClicked: appController.updateStatisticsRange(statsStartField.text, statsEndField.text)
|
onClicked: appController.updateStatisticsRange(statsStartField.text, statsEndField.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimaryButton {
|
|
||||||
text: "导出统计报表"
|
|
||||||
onClicked: statisticsExportDialog.open()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GridLayout {
|
GridLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
columns: phoneLayout ? 1 : (slimLayout ? 3 : (compactLayout ? 1 : 3))
|
columns: phoneLayout ? 2 : (slimLayout ? 3 : (compactLayout ? 1 : 3))
|
||||||
rowSpacing: 10
|
rowSpacing: 10
|
||||||
columnSpacing: 14
|
columnSpacing: 14
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredWidth: phoneLayout ? 1 : 100
|
||||||
Layout.preferredHeight: phoneLayout ? 68 : (slimLayout ? 74 : 100)
|
Layout.preferredHeight: phoneLayout ? 68 : (slimLayout ? 74 : 100)
|
||||||
radius: 8
|
radius: 8
|
||||||
color: panelStrong
|
color: panelStrong
|
||||||
@@ -1437,7 +1436,7 @@ ApplicationWindow {
|
|||||||
anchors.rightMargin: slimLayout ? 12 : 18
|
anchors.rightMargin: slimLayout ? 12 : 18
|
||||||
spacing: 6
|
spacing: 6
|
||||||
Label { text: "统计学生数"; color: ink500 }
|
Label { text: "统计学生数"; color: ink500 }
|
||||||
Label { text: appController.statisticsStudentCount; font.pixelSize: slimLayout ? 22 : (compactLayout ? 28 : 32); font.bold: true; color: ink900 }
|
Label { text: appController.statisticsStudentCount; font.pixelSize: slimLayout ? 22 : (compactLayout ? 28 : 32); font.weight: appBoldWeight; color: ink900 }
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -1453,6 +1452,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredWidth: phoneLayout ? 1 : 100
|
||||||
Layout.preferredHeight: phoneLayout ? 68 : (slimLayout ? 74 : 100)
|
Layout.preferredHeight: phoneLayout ? 68 : (slimLayout ? 74 : 100)
|
||||||
radius: 8
|
radius: 8
|
||||||
color: panelStrong
|
color: panelStrong
|
||||||
@@ -1467,7 +1467,7 @@ ApplicationWindow {
|
|||||||
anchors.rightMargin: slimLayout ? 12 : 18
|
anchors.rightMargin: slimLayout ? 12 : 18
|
||||||
spacing: 6
|
spacing: 6
|
||||||
Label { text: "统计记录数"; color: ink500 }
|
Label { text: "统计记录数"; color: ink500 }
|
||||||
Label { text: appController.statisticsRecordCount; font.pixelSize: slimLayout ? 22 : (compactLayout ? 28 : 32); font.bold: true; color: tealColor }
|
Label { text: appController.statisticsRecordCount; font.pixelSize: slimLayout ? 22 : (compactLayout ? 28 : 32); font.weight: appBoldWeight; color: tealColor }
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -1482,6 +1482,7 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
Layout.columnSpan: phoneLayout ? 2 : 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: phoneLayout ? 68 : (slimLayout ? 74 : 100)
|
Layout.preferredHeight: phoneLayout ? 68 : (slimLayout ? 74 : 100)
|
||||||
radius: 8
|
radius: 8
|
||||||
@@ -1500,7 +1501,7 @@ ApplicationWindow {
|
|||||||
Label {
|
Label {
|
||||||
text: appController.statisticsRangeText
|
text: appController.statisticsRangeText
|
||||||
font.pixelSize: slimLayout ? 14 : (compactLayout ? 18 : 20)
|
font.pixelSize: slimLayout ? 14 : (compactLayout ? 18 : 20)
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
color: goldColor
|
color: goldColor
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
@@ -1553,26 +1554,26 @@ ApplicationWindow {
|
|||||||
anchors.rightMargin: 16
|
anchors.rightMargin: 16
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
Label { text: "学号"; Layout.preferredWidth: statisticsTableCard.studentColWidth; font.bold: true }
|
Label { text: "学号"; Layout.preferredWidth: statisticsTableCard.studentColWidth; font.weight: appBoldWeight }
|
||||||
Label { text: "姓名"; Layout.preferredWidth: statisticsTableCard.nameColWidth; font.bold: true }
|
Label { text: "姓名"; Layout.preferredWidth: statisticsTableCard.nameColWidth; font.weight: appBoldWeight }
|
||||||
Label { text: "总签到次数"; Layout.preferredWidth: statisticsTableCard.totalColWidth; font.bold: true }
|
Label { text: "总签到次数"; Layout.preferredWidth: statisticsTableCard.totalColWidth; font.weight: appBoldWeight }
|
||||||
Label {
|
Label {
|
||||||
text: "今日签到"
|
text: "今日签到"
|
||||||
visible: statisticsTableCard.showTodayColumn
|
visible: statisticsTableCard.showTodayColumn
|
||||||
Layout.preferredWidth: statisticsTableCard.todayColWidth
|
Layout.preferredWidth: statisticsTableCard.todayColWidth
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: "最后签到时间"
|
text: "最后签到时间"
|
||||||
visible: statisticsTableCard.showLastColumn
|
visible: statisticsTableCard.showLastColumn
|
||||||
Layout.preferredWidth: statisticsTableCard.lastColWidth
|
Layout.preferredWidth: statisticsTableCard.lastColWidth
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: "平均置信度"
|
text: "平均置信度"
|
||||||
visible: statisticsTableCard.showAverageColumn
|
visible: statisticsTableCard.showAverageColumn
|
||||||
Layout.preferredWidth: statisticsTableCard.avgColWidth
|
Layout.preferredWidth: statisticsTableCard.avgColWidth
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
}
|
}
|
||||||
@@ -1609,7 +1610,7 @@ ApplicationWindow {
|
|||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
Label { text: model.studentId; Layout.preferredWidth: statisticsTableCard.studentColWidth; color: ink700 }
|
Label { text: model.studentId; Layout.preferredWidth: statisticsTableCard.studentColWidth; color: ink700 }
|
||||||
Label { text: model.name; Layout.preferredWidth: statisticsTableCard.nameColWidth; color: ink900; font.bold: true }
|
Label { text: model.name; Layout.preferredWidth: statisticsTableCard.nameColWidth; color: ink900; font.weight: appBoldWeight }
|
||||||
Label { text: model.totalCount; Layout.preferredWidth: statisticsTableCard.totalColWidth; color: ink700 }
|
Label { text: model.totalCount; Layout.preferredWidth: statisticsTableCard.totalColWidth; color: ink700 }
|
||||||
Label {
|
Label {
|
||||||
text: model.todayCount
|
text: model.todayCount
|
||||||
@@ -1628,7 +1629,7 @@ ApplicationWindow {
|
|||||||
visible: statisticsTableCard.showAverageColumn
|
visible: statisticsTableCard.showAverageColumn
|
||||||
Layout.preferredWidth: statisticsTableCard.avgColWidth
|
Layout.preferredWidth: statisticsTableCard.avgColWidth
|
||||||
color: tealColor
|
color: tealColor
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
}
|
}
|
||||||
@@ -1691,7 +1692,7 @@ ApplicationWindow {
|
|||||||
Label {
|
Label {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
text: "服务器地址"
|
text: "服务器地址"
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
color: ink700
|
color: ink700
|
||||||
}
|
}
|
||||||
AppTextField {
|
AppTextField {
|
||||||
@@ -1785,10 +1786,10 @@ ApplicationWindow {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: 16
|
anchors.leftMargin: 16
|
||||||
anchors.rightMargin: 16
|
anchors.rightMargin: 16
|
||||||
Label { text: "名称"; Layout.preferredWidth: 220; font.bold: true }
|
Label { text: "名称"; Layout.preferredWidth: 220; font.weight: appBoldWeight }
|
||||||
Label { text: "开始"; Layout.preferredWidth: 120; font.bold: true }
|
Label { text: "开始"; Layout.preferredWidth: 120; font.weight: appBoldWeight }
|
||||||
Label { text: "结束"; Layout.preferredWidth: 120; font.bold: true }
|
Label { text: "结束"; Layout.preferredWidth: 120; font.weight: appBoldWeight }
|
||||||
Label { text: "状态"; Layout.preferredWidth: 100; font.bold: true }
|
Label { text: "状态"; Layout.preferredWidth: 100; font.weight: appBoldWeight }
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1829,7 +1830,7 @@ ApplicationWindow {
|
|||||||
text: model.enabled ? "启用" : "禁用"
|
text: model.enabled ? "启用" : "禁用"
|
||||||
Layout.preferredWidth: 100
|
Layout.preferredWidth: 100
|
||||||
color: model.enabled ? successColor : goldColor
|
color: model.enabled ? successColor : goldColor
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
SecondaryButton { text: "编辑"; onClicked: openTimeSlotDialog(index) }
|
SecondaryButton { text: "编辑"; onClicked: openTimeSlotDialog(index) }
|
||||||
@@ -2163,7 +2164,7 @@ ApplicationWindow {
|
|||||||
visible: !model.hasPhoto
|
visible: !model.hasPhoto
|
||||||
text: "无照片"
|
text: "无照片"
|
||||||
color: goldColor
|
color: goldColor
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2171,7 +2172,7 @@ ApplicationWindow {
|
|||||||
text: "ID: " + model.faceId
|
text: "ID: " + model.faceId
|
||||||
color: ink900
|
color: ink900
|
||||||
font.pixelSize: compactLayout ? 15 : 16
|
font.pixelSize: compactLayout ? 15 : 16
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
|
|
||||||
AppTextField {
|
AppTextField {
|
||||||
@@ -2192,7 +2193,7 @@ ApplicationWindow {
|
|||||||
text: model.hasPhoto ? "照片已同步" : "仅检测到记录,无照片"
|
text: model.hasPhoto ? "照片已同步" : "仅检测到记录,无照片"
|
||||||
color: model.hasPhoto ? successColor : goldColor
|
color: model.hasPhoto ? successColor : goldColor
|
||||||
font.pixelSize: narrowLayout ? 11 : 12
|
font.pixelSize: narrowLayout ? 11 : 12
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2281,7 +2282,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: phoneLayout ? 124 : 108
|
Layout.preferredHeight: phoneLayout ? 188 : 164
|
||||||
radius: 8
|
radius: 8
|
||||||
color: "#f8fafb"
|
color: "#f8fafb"
|
||||||
border.color: lineColor
|
border.color: lineColor
|
||||||
@@ -2296,7 +2297,7 @@ ApplicationWindow {
|
|||||||
text: "毕业设计说明"
|
text: "毕业设计说明"
|
||||||
color: ink900
|
color: ink900
|
||||||
font.pixelSize: 16
|
font.pixelSize: 16
|
||||||
font.bold: true
|
font.weight: appBoldWeight
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -2314,6 +2315,22 @@ ApplicationWindow {
|
|||||||
color: ink500
|
color: ink500
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: "仓库地址:https://git.yzfh.eu.cc/yzfh/qt.git"
|
||||||
|
wrapMode: Text.WrapAnywhere
|
||||||
|
color: ink500
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: "查看账号:jtybishe 查看密码:jtybishe"
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
color: ink500
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -91,10 +91,9 @@ bool ApiClient::isConnectionError(QNetworkReply::NetworkError error) const
|
|||||||
|
|
||||||
void ApiClient::emitNetworkError(const QString &prefix, QNetworkReply *reply)
|
void ApiClient::emitNetworkError(const QString &prefix, QNetworkReply *reply)
|
||||||
{
|
{
|
||||||
const QString message = QStringLiteral("%1: %2").arg(prefix, reply->errorString());
|
emit networkError(prefix);
|
||||||
emit networkError(message);
|
|
||||||
if (isConnectionError(reply->error())) {
|
if (isConnectionError(reply->error())) {
|
||||||
emit connectionUnavailable(message);
|
emit connectionUnavailable(prefix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-10
@@ -42,6 +42,18 @@ bool isSuccessStatus(const QString &status)
|
|||||||
|| normalized == QStringLiteral("签到成功")
|
|| normalized == QStringLiteral("签到成功")
|
||||||
|| normalized == QStringLiteral("正常");
|
|| normalized == QStringLiteral("正常");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString conciseStatusMessage(const QString &message)
|
||||||
|
{
|
||||||
|
if (message.contains(QStringLiteral("Error transferring"), Qt::CaseInsensitive)
|
||||||
|
|| message.contains(QStringLiteral("Connection refused"), Qt::CaseInsensitive)
|
||||||
|
|| message.contains(QStringLiteral("Host not found"), Qt::CaseInsensitive)
|
||||||
|
|| message.contains(QStringLiteral("Network unreachable"), Qt::CaseInsensitive)
|
||||||
|
|| message.contains(QStringLiteral("timed out"), Qt::CaseInsensitive)) {
|
||||||
|
return QStringLiteral("服务器未连接,稍后重试");
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AppController::AppController(QObject *parent)
|
AppController::AppController(QObject *parent)
|
||||||
@@ -673,32 +685,30 @@ void AppController::mergeFaceItems()
|
|||||||
|
|
||||||
void AppController::setStatusMessage(const QString &message)
|
void AppController::setStatusMessage(const QString &message)
|
||||||
{
|
{
|
||||||
if (message == m_statusMessage) {
|
const QString displayMessage = conciseStatusMessage(message);
|
||||||
|
if (displayMessage == m_statusMessage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_statusMessage = message;
|
m_statusMessage = displayMessage;
|
||||||
emit statusMessageChanged();
|
emit statusMessageChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppController::startReconnect(const QString &reason)
|
void AppController::startReconnect(const QString &reason)
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(reason)
|
||||||
|
|
||||||
if (!m_reconnecting) {
|
if (!m_reconnecting) {
|
||||||
m_reconnecting = true;
|
m_reconnecting = true;
|
||||||
m_reconnectAttempt = 0;
|
m_reconnectAttempt = 0;
|
||||||
emit reconnectStateChanged();
|
emit reconnectStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString detail = reason.trimmed();
|
|
||||||
if (m_reconnectCheckInFlight || m_reconnectTimer.isActive()) {
|
if (m_reconnectCheckInFlight || m_reconnectTimer.isActive()) {
|
||||||
setStatusMessage(detail.isEmpty()
|
setStatusMessage(QStringLiteral("服务器未连接,稍后重试"));
|
||||||
? QStringLiteral("服务器连接失败,正在等待自动重连")
|
|
||||||
: QStringLiteral("服务器连接失败,正在等待自动重连: %1").arg(detail));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatusMessage(detail.isEmpty()
|
setStatusMessage(QStringLiteral("服务器未连接,稍后重试"));
|
||||||
? QStringLiteral("服务器连接失败,%1 秒后自动重连").arg(ReconnectIntervalMs / 1000)
|
|
||||||
: QStringLiteral("服务器连接失败,%1 秒后自动重连: %2").arg(ReconnectIntervalMs / 1000).arg(detail));
|
|
||||||
scheduleReconnect();
|
scheduleReconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -729,6 +739,6 @@ void AppController::runReconnectCheck()
|
|||||||
m_reconnectCheckInFlight = true;
|
m_reconnectCheckInFlight = true;
|
||||||
++m_reconnectAttempt;
|
++m_reconnectAttempt;
|
||||||
emit reconnectStateChanged();
|
emit reconnectStateChanged();
|
||||||
setStatusMessage(QStringLiteral("正在重连服务器,第 %1 次").arg(m_reconnectAttempt));
|
setStatusMessage(QStringLiteral("正在重连服务器"));
|
||||||
m_apiClient.checkHealth();
|
m_apiClient.checkHealth();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user