终版
This commit is contained in:
+309
-384
@@ -35,6 +35,12 @@ ApplicationWindow {
|
||||
property string statisticsPreset: "最近30天"
|
||||
property int editingTimeSlotIndex: -1
|
||||
property string pendingDeleteFaceId: ""
|
||||
property int pendingStudentIdChangeIndex: -1
|
||||
property string pendingStudentIdChangeNewId: ""
|
||||
property string pendingStudentIdChangeName: ""
|
||||
property string pendingStudentIdChangeClassDisplay: ""
|
||||
property string editingFaceId: ""
|
||||
property string editingFaceOriginalStudentId: ""
|
||||
property string toastMessage: ""
|
||||
property bool toastVisible: false
|
||||
property bool resizingWindow: false
|
||||
@@ -516,6 +522,20 @@ ApplicationWindow {
|
||||
markMappingsDirty();
|
||||
}
|
||||
|
||||
function requestStudentProfileCommit(index, studentId, studentName, classDisplay) {
|
||||
var impactMessage = appController.studentIdChangeImpactMessage(index, studentId);
|
||||
if (impactMessage.length > 0) {
|
||||
pendingStudentIdChangeIndex = index;
|
||||
pendingStudentIdChangeNewId = studentId;
|
||||
pendingStudentIdChangeName = studentName;
|
||||
pendingStudentIdChangeClassDisplay = classDisplay;
|
||||
studentIdChangeDialog.text = impactMessage;
|
||||
studentIdChangeDialog.open();
|
||||
return;
|
||||
}
|
||||
commitStudentProfile(index, studentId, studentName, classDisplay);
|
||||
}
|
||||
|
||||
function studentProfileMatches(searchText, classFilter, bindingFilter, studentId, studentName, classId, className, faceIds) {
|
||||
var normalizedSearch = searchText ? searchText.trim().toLowerCase() : "";
|
||||
var classFilterId = appController.classIdFromDisplay(classFilter);
|
||||
@@ -535,6 +555,39 @@ ApplicationWindow {
|
||||
return haystack.indexOf(normalizedSearch) >= 0;
|
||||
}
|
||||
|
||||
function openFaceEditor(faceId, studentId, studentName, classId) {
|
||||
editingFaceId = faceId;
|
||||
editingFaceOriginalStudentId = studentId;
|
||||
faceEditStudentIdField.text = studentId;
|
||||
faceEditStudentNameField.text = studentName;
|
||||
faceEditClassCombo.currentIndex = classComboIndex(classId);
|
||||
faceEditDialog.open();
|
||||
}
|
||||
|
||||
function saveFaceEditorNow() {
|
||||
var savedLocally = appController.editFaceProfile(editingFaceId,
|
||||
faceEditStudentIdField.text,
|
||||
faceEditStudentNameField.text,
|
||||
appController.classIdFromDisplay(faceEditClassCombo.currentText));
|
||||
if (!savedLocally) {
|
||||
faceEditDialog.open();
|
||||
return;
|
||||
}
|
||||
markMappingsDirty();
|
||||
appController.saveFaceMappings();
|
||||
}
|
||||
|
||||
function requestSaveFaceEditor() {
|
||||
var oldId = editingFaceOriginalStudentId ? editingFaceOriginalStudentId.trim() : "";
|
||||
var newId = faceEditStudentIdField.text ? faceEditStudentIdField.text.trim() : "";
|
||||
if (oldId.length > 0 && oldId !== newId) {
|
||||
faceStudentIdChangeDialog.text = "修改该人脸绑定会影响识别后发送给 STM32 的学号、考勤记录显示、班级筛选和导出结果。确定继续吗?";
|
||||
faceStudentIdChangeDialog.open();
|
||||
return;
|
||||
}
|
||||
saveFaceEditorNow();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
applyRecordFilters();
|
||||
applyStatisticsPreset();
|
||||
@@ -548,7 +601,8 @@ ApplicationWindow {
|
||||
return;
|
||||
}
|
||||
if (appController.statusMessage.indexOf("映射配置已保存") >= 0
|
||||
|| appController.statusMessage.indexOf("映射配置已同步") >= 0) {
|
||||
|| appController.statusMessage.indexOf("映射配置已同步") >= 0
|
||||
|| appController.statusMessage.indexOf("人脸档案已保存") >= 0) {
|
||||
mappingsDirty = false;
|
||||
}
|
||||
toastMessage = appController.statusMessage;
|
||||
@@ -572,7 +626,7 @@ ApplicationWindow {
|
||||
id: recordExportDialog
|
||||
fileMode: FileDialog.SaveFile
|
||||
nameFilters: ["CSV 文件 (*.csv)"]
|
||||
currentFile: "attendance_export.csv"
|
||||
defaultSuffix: "csv"
|
||||
onAccepted: appController.exportFilteredRecords(selectedFile)
|
||||
}
|
||||
|
||||
@@ -580,7 +634,7 @@ ApplicationWindow {
|
||||
id: statisticsExportDialog
|
||||
fileMode: FileDialog.SaveFile
|
||||
nameFilters: ["CSV 文件 (*.csv)"]
|
||||
currentFile: "attendance_statistics.csv"
|
||||
defaultSuffix: "csv"
|
||||
onAccepted: appController.exportStatistics(selectedFile)
|
||||
}
|
||||
|
||||
@@ -784,7 +838,7 @@ ApplicationWindow {
|
||||
spacing: 12
|
||||
|
||||
Label {
|
||||
text: "确定删除 ID 为 " + pendingDeleteFaceId + " 的人脸信息吗?"
|
||||
text: "确定删除人脸编号 " + pendingDeleteFaceId + " 吗?"
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: 18
|
||||
font.weight: appBoldWeight
|
||||
@@ -801,6 +855,116 @@ ApplicationWindow {
|
||||
onAccepted: appController.deleteFace(pendingDeleteFaceId, deleteFaceRecordsCheck.checked)
|
||||
}
|
||||
|
||||
Dialog {
|
||||
id: faceEditDialog
|
||||
modal: true
|
||||
anchors.centerIn: Overlay.overlay
|
||||
width: phoneLayout ? Math.min(window.width - 32, 420) : 460
|
||||
title: "编辑人脸档案"
|
||||
standardButtons: Dialog.Save | Dialog.Cancel
|
||||
|
||||
background: Rectangle {
|
||||
radius: 20
|
||||
color: panelStrong
|
||||
border.color: lineColor
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: 150
|
||||
}
|
||||
NumberAnimation {
|
||||
property: "scale"
|
||||
from: 0.96
|
||||
to: 1.0
|
||||
duration: 180
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: 120
|
||||
}
|
||||
NumberAnimation {
|
||||
property: "scale"
|
||||
from: 1.0
|
||||
to: 0.98
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 12
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: "人脸编号: " + editingFaceId
|
||||
color: ink900
|
||||
font.pixelSize: 18
|
||||
font.weight: appBoldWeight
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
AppTextField {
|
||||
id: faceEditStudentIdField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "真实学号"
|
||||
}
|
||||
|
||||
AppTextField {
|
||||
id: faceEditStudentNameField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "姓名"
|
||||
}
|
||||
|
||||
AppComboBox {
|
||||
id: faceEditClassCombo
|
||||
Layout.fillWidth: true
|
||||
model: appController.classSelectionOptions
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: "人脸编号由 K210 生成,不要改成学号;这里只编辑学号、姓名和班级。留空学号可解除绑定。"
|
||||
wrapMode: Text.WordWrap
|
||||
color: ink500
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: requestSaveFaceEditor()
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: faceStudentIdChangeDialog
|
||||
title: "确认修改绑定学号"
|
||||
buttons: MessageDialog.Yes | MessageDialog.No
|
||||
onAccepted: saveFaceEditorNow()
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: studentIdChangeDialog
|
||||
title: "确认修改学号"
|
||||
buttons: MessageDialog.Yes | MessageDialog.No
|
||||
onAccepted: {
|
||||
commitStudentProfile(pendingStudentIdChangeIndex,
|
||||
pendingStudentIdChangeNewId,
|
||||
pendingStudentIdChangeName,
|
||||
pendingStudentIdChangeClassDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: slimLayout ? 8 : 16
|
||||
@@ -2353,8 +2517,8 @@ ApplicationWindow {
|
||||
|
||||
SectionHeader {
|
||||
Layout.fillWidth: true
|
||||
title: "名称映射配置"
|
||||
description: "让学号和班级编号显示为更清晰的真实名称。"
|
||||
title: "班级管理"
|
||||
description: "新建、命名和删除班级;学生归属在人脸管理的编辑窗口中维护。"
|
||||
accent: tealColor
|
||||
}
|
||||
|
||||
@@ -2367,398 +2531,155 @@ ApplicationWindow {
|
||||
}
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
columns: 2
|
||||
columns: phoneLayout ? 1 : 4
|
||||
columnSpacing: 8
|
||||
rowSpacing: 8
|
||||
|
||||
SecondaryButton {
|
||||
Layout.fillWidth: true
|
||||
visible: mappingsExpanded
|
||||
text: phoneLayout ? "加载" : "从服务器加载"
|
||||
text: phoneLayout ? "加载" : "从服务器加载班级"
|
||||
onClicked: appController.loadMappingsFromServer()
|
||||
}
|
||||
PrimaryButton {
|
||||
Layout.fillWidth: true
|
||||
visible: mappingsExpanded
|
||||
text: "保存映射"
|
||||
text: "保存班级"
|
||||
fillColor: tealColor
|
||||
onClicked: appController.saveMappings()
|
||||
}
|
||||
SecondaryButton {
|
||||
Layout.fillWidth: phoneLayout
|
||||
Layout.preferredWidth: phoneLayout ? -1 : 120
|
||||
visible: mappingsExpanded
|
||||
text: "新增班级"
|
||||
onClicked: {
|
||||
appController.classMappingModel.addEntry("", "");
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: phoneLayout
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: mappingsExpanded
|
||||
text: mappingsDirty ? "有未保存改动" : "配置已保存"
|
||||
color: mappingsDirty ? goldColor : successColor
|
||||
font.pixelSize: 12
|
||||
font.weight: appBoldWeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TabBar {
|
||||
id: mappingTabs
|
||||
ListView {
|
||||
id: classMappingListView
|
||||
visible: mappingsExpanded
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: visible ? 42 : 0
|
||||
Layout.preferredHeight: visible ? Math.max(phoneLayout ? 112 : 64, contentHeight) : 0
|
||||
clip: false
|
||||
interactive: false
|
||||
spacing: 8
|
||||
padding: 0
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
model: appController.classMappingModel
|
||||
|
||||
SegmentTabButton {
|
||||
width: (mappingTabs.width - mappingTabs.spacing) / 2
|
||||
text: "学生档案"
|
||||
}
|
||||
SegmentTabButton {
|
||||
width: (mappingTabs.width - mappingTabs.spacing) / 2
|
||||
text: "班级配置"
|
||||
}
|
||||
}
|
||||
delegate: Rectangle {
|
||||
width: ListView.view.width
|
||||
height: phoneLayout ? 112 : 56
|
||||
property bool hovered: classMappingHoverHandler.hovered
|
||||
radius: 8
|
||||
color: hovered ? "#eef5f4" : (index % 2 === 0 ? "#ffffff" : "#f8f3ec")
|
||||
border.color: hovered ? "#c7dddd" : (index % 2 === 0 ? "#ece4da" : "#e7ded3")
|
||||
|
||||
StackLayout {
|
||||
visible: mappingsExpanded
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: visible ? Math.max(phoneLayout ? 168 : 74, mappingTabs.currentIndex === 0 ? studentProfileListView.contentHeight : classMappingListView.contentHeight) : 0
|
||||
currentIndex: mappingTabs.currentIndex
|
||||
Behavior on color {
|
||||
enabled: !resizingWindow
|
||||
ColorAnimation { duration: 120 }
|
||||
}
|
||||
Behavior on border.color {
|
||||
enabled: !resizingWindow
|
||||
ColorAnimation { duration: 120 }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: "transparent"
|
||||
HoverHandler {
|
||||
id: classMappingHoverHandler
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: studentProfileListView
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
clip: false
|
||||
interactive: false
|
||||
spacing: 8
|
||||
model: appController.studentProfileModel
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
visible: !phoneLayout
|
||||
spacing: 12
|
||||
|
||||
header: ColumnLayout {
|
||||
width: ListView.view.width
|
||||
spacing: 8
|
||||
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
columns: phoneLayout ? 1 : 5
|
||||
columnSpacing: 8
|
||||
rowSpacing: 8
|
||||
|
||||
AppTextField {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "搜索学号、姓名、班级或人脸 ID"
|
||||
text: appController.studentProfileSearchText
|
||||
onTextEdited: appController.studentProfileSearchText = text
|
||||
}
|
||||
|
||||
AppComboBox {
|
||||
Layout.fillWidth: true
|
||||
model: appController.studentProfileClassFilterOptions
|
||||
currentIndex: optionIndex(model, appController.studentProfileClassFilter)
|
||||
onActivated: appController.studentProfileClassFilter = currentText
|
||||
}
|
||||
|
||||
AppComboBox {
|
||||
Layout.fillWidth: true
|
||||
model: ["全部", "已绑定", "未绑定"]
|
||||
currentIndex: optionIndex(model, appController.studentProfileBindingFilter)
|
||||
onActivated: appController.studentProfileBindingFilter = currentText
|
||||
}
|
||||
|
||||
SecondaryButton {
|
||||
Layout.fillWidth: true
|
||||
text: "新增学生"
|
||||
onClicked: {
|
||||
appController.addStudentProfile();
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: mappingsDirty ? "有未保存改动" : "配置已保存"
|
||||
color: mappingsDirty ? goldColor : successColor
|
||||
font.pixelSize: 12
|
||||
font.weight: appBoldWeight
|
||||
}
|
||||
AppTextField {
|
||||
id: classKeyField
|
||||
Layout.preferredWidth: 220
|
||||
text: model.entryKey
|
||||
placeholderText: "班级编号"
|
||||
onEditingFinished: {
|
||||
appController.classMappingModel.updateEntry(index, text, classValueField.text);
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: studentProfileRow
|
||||
width: ListView.view.width
|
||||
height: visible ? (phoneLayout ? 168 : 64) : 0
|
||||
visible: studentProfileMatches(appController.studentProfileSearchText,
|
||||
appController.studentProfileClassFilter,
|
||||
appController.studentProfileBindingFilter,
|
||||
model.studentId,
|
||||
model.studentName,
|
||||
model.classId,
|
||||
model.className,
|
||||
model.faceIds)
|
||||
property int rowIndex: index
|
||||
property string rowClassId: model.classId
|
||||
property bool hovered: studentProfileHoverHandler.hovered
|
||||
radius: 8
|
||||
color: hovered ? "#eef5f4" : (index % 2 === 0 ? "#ffffff" : "#f8f3ec")
|
||||
border.color: hovered ? "#c7dddd" : (index % 2 === 0 ? "#ece4da" : "#e7ded3")
|
||||
|
||||
Behavior on color {
|
||||
enabled: !resizingWindow
|
||||
ColorAnimation {
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
Behavior on border.color {
|
||||
enabled: !resizingWindow
|
||||
ColorAnimation {
|
||||
duration: 120
|
||||
}
|
||||
AppTextField {
|
||||
id: classValueField
|
||||
Layout.preferredWidth: 260
|
||||
text: model.entryValue
|
||||
placeholderText: "班级名称"
|
||||
onEditingFinished: {
|
||||
appController.classMappingModel.updateEntry(index, classKeyField.text, text);
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: studentProfileHoverHandler
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
visible: !phoneLayout
|
||||
spacing: 10
|
||||
|
||||
AppTextField {
|
||||
id: profileStudentIdField
|
||||
Layout.preferredWidth: 160
|
||||
text: model.studentId
|
||||
placeholderText: "学号"
|
||||
onEditingFinished: commitStudentProfile(studentProfileRow.rowIndex, text, profileStudentNameField.text, profileClassCombo.currentText)
|
||||
}
|
||||
|
||||
AppTextField {
|
||||
id: profileStudentNameField
|
||||
Layout.preferredWidth: 150
|
||||
text: model.studentName
|
||||
placeholderText: "姓名"
|
||||
onEditingFinished: commitStudentProfile(studentProfileRow.rowIndex, profileStudentIdField.text, text, profileClassCombo.currentText)
|
||||
}
|
||||
|
||||
AppComboBox {
|
||||
id: profileClassCombo
|
||||
Layout.preferredWidth: 220
|
||||
model: appController.classSelectionOptions
|
||||
Component.onCompleted: currentIndex = classComboIndex(studentProfileRow.rowClassId)
|
||||
onModelChanged: currentIndex = classComboIndex(studentProfileRow.rowClassId)
|
||||
onActivated: commitStudentProfile(studentProfileRow.rowIndex, profileStudentIdField.text, profileStudentNameField.text, currentText)
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.preferredWidth: 160
|
||||
text: model.faceIds.length > 0 ? model.faceIds : "未绑定人脸"
|
||||
color: model.faceIds.length > 0 ? successColor : ink500
|
||||
font.pixelSize: 12
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
SecondaryButton {
|
||||
text: "删除"
|
||||
onClicked: {
|
||||
appController.removeStudentProfile(studentProfileRow.rowIndex);
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
visible: phoneLayout
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
AppTextField {
|
||||
id: profileStudentIdFieldPhone
|
||||
Layout.fillWidth: true
|
||||
text: model.studentId
|
||||
placeholderText: "学号"
|
||||
onEditingFinished: commitStudentProfile(studentProfileRow.rowIndex, text, profileStudentNameFieldPhone.text, profileClassComboPhone.currentText)
|
||||
}
|
||||
|
||||
AppTextField {
|
||||
id: profileStudentNameFieldPhone
|
||||
Layout.fillWidth: true
|
||||
text: model.studentName
|
||||
placeholderText: "姓名"
|
||||
onEditingFinished: commitStudentProfile(studentProfileRow.rowIndex, profileStudentIdFieldPhone.text, text, profileClassComboPhone.currentText)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
AppComboBox {
|
||||
id: profileClassComboPhone
|
||||
Layout.fillWidth: true
|
||||
model: appController.classSelectionOptions
|
||||
Component.onCompleted: currentIndex = classComboIndex(studentProfileRow.rowClassId)
|
||||
onModelChanged: currentIndex = classComboIndex(studentProfileRow.rowClassId)
|
||||
onActivated: commitStudentProfile(studentProfileRow.rowIndex, profileStudentIdFieldPhone.text, profileStudentNameFieldPhone.text, currentText)
|
||||
}
|
||||
|
||||
SecondaryButton {
|
||||
Layout.preferredWidth: 76
|
||||
text: "删除"
|
||||
onClicked: {
|
||||
appController.removeStudentProfile(studentProfileRow.rowIndex);
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: model.faceIds.length > 0 ? "绑定人脸: " + model.faceIds : "未绑定人脸"
|
||||
color: model.faceIds.length > 0 ? successColor : ink500
|
||||
font.pixelSize: 12
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
SecondaryButton {
|
||||
text: "删除"
|
||||
onClicked: {
|
||||
if (appController.removeClassMapping(index))
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: "transparent"
|
||||
|
||||
ListView {
|
||||
id: classMappingListView
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
clip: false
|
||||
interactive: false
|
||||
anchors.margins: 12
|
||||
visible: phoneLayout
|
||||
spacing: 8
|
||||
model: appController.classMappingModel
|
||||
|
||||
header: RowLayout {
|
||||
width: ListView.view.width
|
||||
spacing: 12
|
||||
AppTextField {
|
||||
id: classKeyFieldPhone
|
||||
Layout.fillWidth: true
|
||||
text: model.entryKey
|
||||
placeholderText: "班级编号"
|
||||
onEditingFinished: {
|
||||
appController.classMappingModel.updateEntry(index, text, classValueFieldPhone.text);
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
SecondaryButton {
|
||||
text: "新增班级"
|
||||
onClicked: {
|
||||
appController.classMappingModel.addEntry("", "");
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
AppTextField {
|
||||
id: classValueFieldPhone
|
||||
Layout.fillWidth: true
|
||||
text: model.entryValue
|
||||
placeholderText: "班级名称"
|
||||
onEditingFinished: {
|
||||
appController.classMappingModel.updateEntry(index, classKeyFieldPhone.text, text);
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
width: ListView.view.width
|
||||
height: phoneLayout ? 112 : 56
|
||||
property bool hovered: classMappingHoverHandler.hovered
|
||||
radius: 8
|
||||
color: hovered ? "#eef5f4" : (index % 2 === 0 ? "#ffffff" : "#f8f3ec")
|
||||
border.color: hovered ? "#c7dddd" : (index % 2 === 0 ? "#ece4da" : "#e7ded3")
|
||||
|
||||
Behavior on color {
|
||||
enabled: !resizingWindow
|
||||
ColorAnimation {
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
Behavior on border.color {
|
||||
enabled: !resizingWindow
|
||||
ColorAnimation {
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: classMappingHoverHandler
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
visible: !phoneLayout
|
||||
spacing: 12
|
||||
|
||||
AppTextField {
|
||||
id: classKeyField
|
||||
Layout.preferredWidth: 220
|
||||
text: model.entryKey
|
||||
placeholderText: "班级编号"
|
||||
onEditingFinished: {
|
||||
appController.classMappingModel.updateEntry(index, text, classValueField.text);
|
||||
SecondaryButton {
|
||||
Layout.preferredWidth: 76
|
||||
text: "删除"
|
||||
onClicked: {
|
||||
if (appController.removeClassMapping(index))
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
AppTextField {
|
||||
id: classValueField
|
||||
Layout.preferredWidth: 260
|
||||
text: model.entryValue
|
||||
placeholderText: "班级名称"
|
||||
onEditingFinished: {
|
||||
appController.classMappingModel.updateEntry(index, classKeyField.text, text);
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
SecondaryButton {
|
||||
text: "删除"
|
||||
onClicked: {
|
||||
appController.classMappingModel.removeEntry(index);
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
visible: phoneLayout
|
||||
spacing: 8
|
||||
|
||||
AppTextField {
|
||||
id: classKeyFieldPhone
|
||||
Layout.fillWidth: true
|
||||
text: model.entryKey
|
||||
placeholderText: "班级编号"
|
||||
onEditingFinished: {
|
||||
appController.classMappingModel.updateEntry(index, text, classValueFieldPhone.text);
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
AppTextField {
|
||||
id: classValueFieldPhone
|
||||
Layout.fillWidth: true
|
||||
text: model.entryValue
|
||||
placeholderText: "班级名称"
|
||||
onEditingFinished: {
|
||||
appController.classMappingModel.updateEntry(index, classKeyFieldPhone.text, text);
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
SecondaryButton {
|
||||
Layout.preferredWidth: 76
|
||||
text: "删除"
|
||||
onClicked: {
|
||||
appController.classMappingModel.removeEntry(index);
|
||||
markMappingsDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2817,8 +2738,8 @@ ApplicationWindow {
|
||||
|
||||
SectionHeader {
|
||||
Layout.fillWidth: true
|
||||
title: "K210 人脸照片与学号映射"
|
||||
description: "同步照片状态,维护人脸与真实学号的对应关系。"
|
||||
title: "K210 人脸照片与档案绑定"
|
||||
description: "同步照片状态,维护人脸、学号、姓名和班级信息。"
|
||||
accent: goldColor
|
||||
}
|
||||
|
||||
@@ -2832,7 +2753,7 @@ ApplicationWindow {
|
||||
onClicked: appController.refreshFaces()
|
||||
}
|
||||
PrimaryButton {
|
||||
text: "保存映射"
|
||||
text: "保存档案"
|
||||
onClicked: appController.saveFaceMappings()
|
||||
}
|
||||
}
|
||||
@@ -2853,12 +2774,12 @@ ApplicationWindow {
|
||||
clip: true
|
||||
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 ? 462 : 424
|
||||
cellHeight: phoneLayout ? 444 : 404
|
||||
model: appController.faceModel
|
||||
|
||||
delegate: Rectangle {
|
||||
width: Math.min(phoneLayout ? 300 : 244, GridView.view.cellWidth - 18)
|
||||
height: phoneLayout ? 438 : 400
|
||||
height: phoneLayout ? 420 : 380
|
||||
property bool hovered: faceHoverHandler.hovered
|
||||
radius: 8
|
||||
color: hovered ? "#fff6ef" : "#ffffff"
|
||||
@@ -2912,23 +2833,25 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "ID: " + model.faceId
|
||||
text: "人脸编号: " + model.faceId
|
||||
color: ink900
|
||||
font.pixelSize: compactLayout ? 15 : 16
|
||||
font.weight: appBoldWeight
|
||||
}
|
||||
|
||||
AppTextField {
|
||||
Layout.fillWidth: true
|
||||
text: model.studentId
|
||||
placeholderText: narrowLayout ? "学号" : "输入真实学号"
|
||||
onEditingFinished: appController.updateFaceStudentId(index, text)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: model.studentId.length > 0 ? "学号: " + model.studentId : "学号: 未绑定"
|
||||
color: model.studentId.length > 0 ? ink700 : goldColor
|
||||
font.pixelSize: 12
|
||||
font.weight: model.studentId.length > 0 ? appBoldWeight : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: model.studentName.length > 0 ? "姓名: " + model.studentName : "姓名: 未建档"
|
||||
@@ -2950,12 +2873,12 @@ ApplicationWindow {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 34
|
||||
radius: 8
|
||||
color: model.studentId.length === 0 ? "#f9ebcf" : (model.studentName.length > 0 ? "#e6f4ec" : "#fce8e5")
|
||||
color: model.studentId.length === 0 ? "#f9ebcf" : (model.studentName.length > 0 ? "#e6f4ec" : "#f9ebcf")
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: model.studentId.length === 0 ? "未绑定学生" : (model.studentName.length > 0 ? "已绑定学生档案" : "学号未建档")
|
||||
color: model.studentId.length === 0 ? goldColor : (model.studentName.length > 0 ? successColor : dangerColor)
|
||||
text: model.studentId.length === 0 ? "未绑定学生" : (model.studentName.length > 0 ? "已绑定学生档案" : "待补全学生信息")
|
||||
color: model.studentId.length === 0 ? goldColor : (model.studentName.length > 0 ? successColor : goldColor)
|
||||
font.pixelSize: narrowLayout ? 11 : 12
|
||||
font.weight: appBoldWeight
|
||||
}
|
||||
@@ -2980,13 +2903,23 @@ ApplicationWindow {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
DangerButton {
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
text: "删除该人脸"
|
||||
onClicked: {
|
||||
pendingDeleteFaceId = model.faceId;
|
||||
deleteFaceRecordsCheck.checked = false;
|
||||
deleteFaceDialog.open();
|
||||
|
||||
SecondaryButton {
|
||||
Layout.fillWidth: true
|
||||
text: "编辑"
|
||||
onClicked: openFaceEditor(model.faceId, model.studentId, model.studentName, model.classId)
|
||||
}
|
||||
|
||||
DangerButton {
|
||||
Layout.fillWidth: true
|
||||
text: "删除"
|
||||
onClicked: {
|
||||
pendingDeleteFaceId = model.faceId;
|
||||
deleteFaceRecordsCheck.checked = false;
|
||||
deleteFaceDialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3040,12 +2973,10 @@ ApplicationWindow {
|
||||
anchors.margins: phoneLayout ? 16 : 24
|
||||
clip: true
|
||||
contentWidth: availableWidth
|
||||
contentHeight: aboutContentLayout.height
|
||||
|
||||
ColumnLayout {
|
||||
id: aboutContentLayout
|
||||
width: aboutScrollView.availableWidth
|
||||
height: Math.max(aboutScrollView.availableHeight, implicitHeight)
|
||||
spacing: phoneLayout ? 12 : 18
|
||||
|
||||
SectionHeader {
|
||||
@@ -3079,12 +3010,6 @@ ApplicationWindow {
|
||||
lineHeight: 1.25
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 0
|
||||
Layout.preferredHeight: 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: thesisInfoTitle.implicitHeight + thesisInfoBody.paintedHeight + thesisInfoLayout.spacing + 32
|
||||
|
||||
Reference in New Issue
Block a user