|
@@ -1,7 +1,6 @@
|
|
|
import QtQuick 2.12
|
|
import QtQuick 2.12
|
|
|
import QtQuick.Templates 2.12
|
|
import QtQuick.Templates 2.12
|
|
|
import QtQuick.Layouts 1.12
|
|
import QtQuick.Layouts 1.12
|
|
|
-import QtQuick.Shapes 1.12
|
|
|
|
|
import QtGraphicalEffects 1.12
|
|
import QtGraphicalEffects 1.12
|
|
|
import "./components"
|
|
import "./components"
|
|
|
import Loto 1.0
|
|
import Loto 1.0
|
|
@@ -31,12 +30,38 @@ Rectangle {
|
|
|
|
|
|
|
|
property int faceAreaSize: 500
|
|
property int faceAreaSize: 500
|
|
|
|
|
|
|
|
- // 刷卡登录状态
|
|
|
|
|
- property bool cardLoginLoading: false // 刷卡登录loading状态
|
|
|
|
|
|
|
+ // 刷卡/作业票请求 loading
|
|
|
|
|
+ property bool cardLoginLoading: false
|
|
|
|
|
+ property bool jobTicketsLoading: false // 登录成功后请求作业票列表时的 loading
|
|
|
property bool showCardLoginError: false // 是否显示刷卡登录错误提示
|
|
property bool showCardLoginError: false // 是否显示刷卡登录错误提示
|
|
|
property string cardLoginErrorText: "" // 刷卡登录错误提示内容
|
|
property string cardLoginErrorText: "" // 刷卡登录错误提示内容
|
|
|
property var cardLoginErrorDialog: null // 错误提示对话框对象
|
|
property var cardLoginErrorDialog: null // 错误提示对话框对象
|
|
|
|
|
|
|
|
|
|
+ // 账号密码登录:loading 与错误弹窗
|
|
|
|
|
+ property bool passwordLoginLoading: false
|
|
|
|
|
+ property var passwordLoginErrorDialog: null
|
|
|
|
|
+
|
|
|
|
|
+ // API 超时弹窗(408):处理超时,请稍后重试。确认按钮 10 秒倒计时,关闭后刷新
|
|
|
|
|
+ property var timeoutDialog: null
|
|
|
|
|
+ property int timeoutCountdown: 10
|
|
|
|
|
+ property var timeoutRefreshCallback: null
|
|
|
|
|
+
|
|
|
|
|
+ // 人脸登录成功:显示「nickName、登录成功」提示,300ms 后关闭并执行刷卡成功逻辑
|
|
|
|
|
+ property bool faceLoginSuccessPending: false // 为 true 时停止采集、API、队列
|
|
|
|
|
+ property bool showFaceLoginSuccessPrompt: false
|
|
|
|
|
+ property string faceLoginSuccessNickName: ""
|
|
|
|
|
+
|
|
|
|
|
+ // 人脸弹窗刚打开时忽略旧请求的 success,避免二次打开被误判为成功
|
|
|
|
|
+ property bool faceDialogJustOpened: false
|
|
|
|
|
+
|
|
|
|
|
+ // 人脸登录错误弹窗:API 返回错误时弹窗,确认按钮 10 秒倒计时自动关闭
|
|
|
|
|
+ property var faceLoginErrorDialog: null
|
|
|
|
|
+ property int faceLoginErrorCountdown: 10
|
|
|
|
|
+ // 累计人脸 API 非 200 次数(任意非 200 都统计),满 5 次则停止视频、停止识别并弹窗
|
|
|
|
|
+ property int faceLoginError902Count: 0
|
|
|
|
|
+ // 已达 5 次非 200,弹窗已显示,停止 API/采集定时器
|
|
|
|
|
+ property bool faceLoginError902Reached: false
|
|
|
|
|
+
|
|
|
Component.onCompleted: {
|
|
Component.onCompleted: {
|
|
|
isCardInput = true;
|
|
isCardInput = true;
|
|
|
// 确保页面加载时获取焦点,以便接收键盘事件
|
|
// 确保页面加载时获取焦点,以便接收键盘事件
|
|
@@ -44,6 +69,62 @@ Rectangle {
|
|
|
console.log("[Login.qml] 页面加载完成,已获取焦点");
|
|
console.log("[Login.qml] 页面加载完成,已获取焦点");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 释放人脸识别资源:清空队列与缓存,停止请求(关闭人脸弹窗或登录成功时调用)
|
|
|
|
|
+ function clearFaceLoginResources() {
|
|
|
|
|
+ if (typeof httpFaceLogin !== "undefined" && httpFaceLogin.clearFaceQueue)
|
|
|
|
|
+ httpFaceLogin.clearFaceQueue();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭人脸登录错误弹窗(不关视频框,仅关弹窗)
|
|
|
|
|
+ function closeFaceLoginErrorDialog() {
|
|
|
|
|
+ if (faceLoginErrorDialog !== null) {
|
|
|
|
|
+ faceLoginErrorAutoCloseTimer.stop();
|
|
|
|
|
+ faceLoginErrorDialog.visible = false;
|
|
|
|
|
+ faceLoginErrorDialog.destroy();
|
|
|
|
|
+ faceLoginErrorDialog = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 人脸登录错误:关闭弹窗 + 关闭视频框 + 清理资源
|
|
|
|
|
+ function closeFaceLoginErrorAndDialog() {
|
|
|
|
|
+ faceLoginErrorAutoCloseTimer.stop();
|
|
|
|
|
+ if (faceLoginErrorDialog !== null) {
|
|
|
|
|
+ faceLoginErrorDialog.visible = false;
|
|
|
|
|
+ faceLoginErrorDialog.destroy();
|
|
|
|
|
+ faceLoginErrorDialog = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ faceLoginError902Count = 0;
|
|
|
|
|
+ faceLoginError902Reached = false;
|
|
|
|
|
+ InteractiveFace.cameraImageStop();
|
|
|
|
|
+ if (typeof httpFaceLogin !== "undefined" && httpFaceLogin.clearFaceQueue)
|
|
|
|
|
+ httpFaceLogin.clearFaceQueue();
|
|
|
|
|
+ loginInput.visible = false;
|
|
|
|
|
+ loginInput.sourceComponent = null;
|
|
|
|
|
+ clearFaceLoginResources();
|
|
|
|
|
+ faceLoginSuccessPending = false;
|
|
|
|
|
+ control.forceActiveFocus();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 显示人脸登录错误弹窗:错误信息 + 确认按钮 10 秒倒计时
|
|
|
|
|
+ function showFaceLoginErrorDialog(msg) {
|
|
|
|
|
+ closeFaceLoginErrorDialog();
|
|
|
|
|
+ faceLoginErrorCountdown = 10;
|
|
|
|
|
+ var component = Qt.createComponent("components/AlertDialog.qml");
|
|
|
|
|
+ if (component.status === Component.Ready) {
|
|
|
|
|
+ faceLoginErrorDialog = component.createObject(appAlertDialog, {
|
|
|
|
|
+ messageTypeName: "", // 仅保留下方一条提示
|
|
|
|
|
+ messageValue: msg || "人脸识别登录失败,请重试",
|
|
|
|
|
+ messageIconCharacter: "\uf071",
|
|
|
|
|
+ confirmBtnText: "确认(10)"
|
|
|
|
|
+ });
|
|
|
|
|
+ faceLoginErrorDialog.parent = appAlertDialog;
|
|
|
|
|
+ faceLoginErrorDialog.confirm.connect(function () {
|
|
|
|
|
+ closeFaceLoginErrorAndDialog();
|
|
|
|
|
+ });
|
|
|
|
|
+ faceLoginErrorAutoCloseTimer.restart();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 关闭刷卡登录错误提示对话框
|
|
// 关闭刷卡登录错误提示对话框
|
|
|
function closeCardLoginErrorDialog() {
|
|
function closeCardLoginErrorDialog() {
|
|
|
console.log("[Login.qml] 关闭错误提示对话框");
|
|
console.log("[Login.qml] 关闭错误提示对话框");
|
|
@@ -56,6 +137,16 @@ Rectangle {
|
|
|
cardLoginErrorAutoCloseTimer.stop();
|
|
cardLoginErrorAutoCloseTimer.stop();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 关闭账号密码登录错误提示对话框
|
|
|
|
|
+ function closePasswordLoginErrorDialog() {
|
|
|
|
|
+ if (passwordLoginErrorDialog !== null) {
|
|
|
|
|
+ passwordLoginErrorDialog.visible = false;
|
|
|
|
|
+ passwordLoginErrorDialog.destroy();
|
|
|
|
|
+ passwordLoginErrorDialog = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ passwordLoginErrorAutoCloseTimer.stop();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 显示刷卡登录错误提示对话框
|
|
// 显示刷卡登录错误提示对话框
|
|
|
function showCardLoginErrorDialog(errorMsg) {
|
|
function showCardLoginErrorDialog(errorMsg) {
|
|
|
console.log("[Login.qml] 显示错误提示对话框,内容:", errorMsg);
|
|
console.log("[Login.qml] 显示错误提示对话框,内容:", errorMsg);
|
|
@@ -86,6 +177,60 @@ Rectangle {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 关闭 API 超时弹窗并执行刷新回调
|
|
|
|
|
+ function closeTimeoutDialog() {
|
|
|
|
|
+ timeoutAutoCloseTimer.stop();
|
|
|
|
|
+ if (timeoutDialog !== null) {
|
|
|
|
|
+ timeoutDialog.visible = false;
|
|
|
|
|
+ timeoutDialog.destroy();
|
|
|
|
|
+ timeoutDialog = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ timeoutRefreshCallback = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 显示 API 超时弹窗:处理超时,请稍后重试。确认(10) 倒计时,关闭后执行 refreshCallback(如刷新页面)
|
|
|
|
|
+ function showTimeoutDialog(refreshCallback) {
|
|
|
|
|
+ closeTimeoutDialog();
|
|
|
|
|
+ timeoutCountdown = 10;
|
|
|
|
|
+ timeoutRefreshCallback = refreshCallback || null;
|
|
|
|
|
+ var component = Qt.createComponent("components/AlertDialog.qml");
|
|
|
|
|
+ if (component.status === Component.Ready) {
|
|
|
|
|
+ timeoutDialog = component.createObject(appAlertDialog, {
|
|
|
|
|
+ messageTypeName: "",
|
|
|
|
|
+ messageValue: "处理超时,请稍后重试。",
|
|
|
|
|
+ messageIconCharacter: "\uf071",
|
|
|
|
|
+ confirmBtnText: "确认(10)"
|
|
|
|
|
+ });
|
|
|
|
|
+ timeoutDialog.parent = appAlertDialog;
|
|
|
|
|
+ timeoutDialog.confirm.connect(function () {
|
|
|
|
|
+ if (typeof timeoutRefreshCallback === "function")
|
|
|
|
|
+ timeoutRefreshCallback();
|
|
|
|
|
+ closeTimeoutDialog();
|
|
|
|
|
+ });
|
|
|
|
|
+ timeoutAutoCloseTimer.restart();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 显示账号密码登录错误提示对话框(与人脸/刷卡一致,使用 AlertDialog)
|
|
|
|
|
+ function showPasswordLoginErrorDialog(msg) {
|
|
|
|
|
+ closePasswordLoginErrorDialog();
|
|
|
|
|
+ var component = Qt.createComponent("components/AlertDialog.qml");
|
|
|
|
|
+ if (component.status === Component.Ready) {
|
|
|
|
|
+ passwordLoginErrorDialog = component.createObject(appAlertDialog, {
|
|
|
|
|
+ messageTypeName: "登录失败",
|
|
|
|
|
+ messageValue: msg || "用户名或者密码错误",
|
|
|
|
|
+ messageIconCharacter: "\uf071"
|
|
|
|
|
+ });
|
|
|
|
|
+ passwordLoginErrorDialog.parent = appAlertDialog;
|
|
|
|
|
+ passwordLoginErrorDialog.confirm.connect(function () {
|
|
|
|
|
+ closePasswordLoginErrorDialog();
|
|
|
|
|
+ });
|
|
|
|
|
+ passwordLoginErrorAutoCloseTimer.restart();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log("[Login.qml] 加载AlertDialog组件失败:", component.errorString());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
HttpPasswordLogin {
|
|
HttpPasswordLogin {
|
|
|
id: httpPasswordLogin
|
|
id: httpPasswordLogin
|
|
|
|
|
|
|
@@ -103,12 +248,17 @@ Rectangle {
|
|
|
Connections {
|
|
Connections {
|
|
|
target: httpPasswordLogin
|
|
target: httpPasswordLogin
|
|
|
function onSignalLoginReturnStat(stat, data) {
|
|
function onSignalLoginReturnStat(stat, data) {
|
|
|
- if(stat === 0) {
|
|
|
|
|
- loginSuccess = true
|
|
|
|
|
|
|
+ passwordLoginLoading = false;
|
|
|
|
|
+ if (stat === 408) {
|
|
|
|
|
+ showTimeoutDialog(function () {
|
|
|
|
|
+ // 刷新:登录页保持,仅关闭 loading 与弹窗
|
|
|
|
|
+ });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (stat === 0) {
|
|
|
|
|
+ loginSuccess = true;
|
|
|
} else {
|
|
} else {
|
|
|
- showErrorLogin = true;
|
|
|
|
|
- errorNoticeTimeout = 3;
|
|
|
|
|
- errorNoticText = data;
|
|
|
|
|
|
|
+ showPasswordLoginErrorDialog(data || "用户名或者密码错误");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -127,20 +277,117 @@ Rectangle {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ HttpFaceLogin {
|
|
|
|
|
+ id: httpFaceLogin
|
|
|
|
|
+
|
|
|
|
|
+ // 人脸登录 HTTP 在 HttpFaceLogin 内自建 QNAM 完成,不占用 HttpClient
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Connections {
|
|
|
|
|
+ target: httpFaceLogin
|
|
|
|
|
+ function onSignalFaceLoginSuccess(nickName) {
|
|
|
|
|
+ // 弹窗未显示或不是人脸弹窗:忽略(可能是上次会话的延迟回调)
|
|
|
|
|
+ if (!loginInput.visible || loginInput.sourceComponent !== faceInputDelegate) return
|
|
|
|
|
+ // 刚打开弹窗的短时间内忽略,避免上次会话的 QueuedConnection 回调误触发
|
|
|
|
|
+ if (control.faceDialogJustOpened) return
|
|
|
|
|
+ // 只处理第一次成功,避免多请求同时返回时重复关界面、重复逻辑
|
|
|
|
|
+ if (control.faceLoginSuccessPending) return
|
|
|
|
|
+ control.faceLoginSuccessPending = true
|
|
|
|
|
+ // API 返回成功即释放资源:停摄像头并释放帧缓冲、清空人脸队列与缓存、释放相关引用
|
|
|
|
|
+ InteractiveFace.cameraImageStop()
|
|
|
|
|
+ if (typeof httpFaceLogin !== "undefined" && httpFaceLogin.clearFaceQueue)
|
|
|
|
|
+ httpFaceLogin.clearFaceQueue()
|
|
|
|
|
+ if (typeof control.clearFaceLoginResources === "function")
|
|
|
|
|
+ control.clearFaceLoginResources()
|
|
|
|
|
+ control.faceLoginSuccessNickName = nickName || "用户"
|
|
|
|
|
+ control.showFaceLoginSuccessPrompt = true
|
|
|
|
|
+ faceLoginSuccessCloseTimer.start()
|
|
|
|
|
+ }
|
|
|
|
|
+ function onSignalFaceLoginError(msg) {
|
|
|
|
|
+ if (msg === "处理超时,请稍后重试。") {
|
|
|
|
|
+ control.showTimeoutDialog(control.closeFaceLoginErrorAndDialog);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ var prevCount = control.faceLoginError902Count || 0;
|
|
|
|
|
+ control.faceLoginError902Count = prevCount + 1;
|
|
|
|
|
+ var curCount = control.faceLoginError902Count;
|
|
|
|
|
+ console.log("[Login.qml] 人脸 API 非200,累计:", curCount, "/5, msg:", msg);
|
|
|
|
|
+ if (curCount >= 5) {
|
|
|
|
|
+ console.log("[Login.qml] 非200 已达 5 次,停止视频与识别并弹窗");
|
|
|
|
|
+ control.faceLoginError902Count = 0;
|
|
|
|
|
+ control.faceLoginError902Reached = true;
|
|
|
|
|
+ InteractiveFace.cameraImageStop();
|
|
|
|
|
+ if (typeof httpFaceLogin !== "undefined" && httpFaceLogin.clearFaceQueue)
|
|
|
|
|
+ httpFaceLogin.clearFaceQueue();
|
|
|
|
|
+ if (typeof control.clearFaceLoginResources === "function")
|
|
|
|
|
+ control.clearFaceLoginResources();
|
|
|
|
|
+ control.showFaceLoginErrorDialog("人脸识别登录失败,请重试");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ console.log("[Login.qml] 人脸登录 API 错误,继续识别:", msg);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Timer {
|
|
|
|
|
+ id: faceLoginSuccessCloseTimer
|
|
|
|
|
+ interval: 300
|
|
|
|
|
+ repeat: false
|
|
|
|
|
+ running: false
|
|
|
|
|
+ onTriggered: {
|
|
|
|
|
+ // 按刷卡成功逻辑:关人脸弹窗、清理、跳转作业票
|
|
|
|
|
+ control.faceDialogJustOpened = false
|
|
|
|
|
+ control.faceLoginError902Count = 0
|
|
|
|
|
+ control.faceLoginError902Reached = false
|
|
|
|
|
+ if (typeof httpFaceLogin !== "undefined" && httpFaceLogin.clearFaceQueue)
|
|
|
|
|
+ httpFaceLogin.clearFaceQueue()
|
|
|
|
|
+ loginInput.visible = false
|
|
|
|
|
+ loginInput.sourceComponent = null
|
|
|
|
|
+ control.showFaceLoginSuccessPrompt = false
|
|
|
|
|
+ control.faceLoginSuccessPending = false
|
|
|
|
|
+ control.loginSuccess = true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 人脸弹窗打开后一段时间内忽略旧会话的 success 回调
|
|
|
|
|
+ Timer {
|
|
|
|
|
+ id: faceDialogJustOpenedTimer
|
|
|
|
|
+ interval: 600
|
|
|
|
|
+ repeat: false
|
|
|
|
|
+ running: false
|
|
|
|
|
+ onTriggered: { control.faceDialogJustOpened = false }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 人脸登录错误弹窗:确认按钮 10 秒倒计时,每秒更新按钮文字,到 0 自动关闭
|
|
|
|
|
+ Timer {
|
|
|
|
|
+ id: faceLoginErrorAutoCloseTimer
|
|
|
|
|
+ interval: 1000
|
|
|
|
|
+ repeat: true
|
|
|
|
|
+ running: false
|
|
|
|
|
+ onTriggered: {
|
|
|
|
|
+ faceLoginErrorCountdown--
|
|
|
|
|
+ if (control.faceLoginErrorDialog)
|
|
|
|
|
+ control.faceLoginErrorDialog.confirmBtnText = "确认(" + Math.max(0, faceLoginErrorCountdown) + ")"
|
|
|
|
|
+ if (faceLoginErrorCountdown <= 0) {
|
|
|
|
|
+ faceLoginErrorAutoCloseTimer.stop()
|
|
|
|
|
+ closeFaceLoginErrorAndDialog()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
Connections {
|
|
Connections {
|
|
|
target: httpCardLogin
|
|
target: httpCardLogin
|
|
|
function onSignalLoginReturnStat(stat, data) {
|
|
function onSignalLoginReturnStat(stat, data) {
|
|
|
console.log("[Login.qml] 收到登录响应信号,stat:", stat, ",data:", data);
|
|
console.log("[Login.qml] 收到登录响应信号,stat:", stat, ",data:", data);
|
|
|
- // 隐藏loading
|
|
|
|
|
cardLoginLoading = false;
|
|
cardLoginLoading = false;
|
|
|
- console.log("[Login.qml] 隐藏loading");
|
|
|
|
|
-
|
|
|
|
|
- if(stat === 0) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (stat === 0) {
|
|
|
console.log("[Login.qml] 登录成功!");
|
|
console.log("[Login.qml] 登录成功!");
|
|
|
- loginSuccess = true
|
|
|
|
|
|
|
+ loginSuccess = true;
|
|
|
|
|
+ } else if (stat === 408) {
|
|
|
|
|
+ showTimeoutDialog(function () {
|
|
|
|
|
+ // 刷新:登录页保持
|
|
|
|
|
+ });
|
|
|
} else {
|
|
} else {
|
|
|
- console.log("[Login.qml] 登录失败,显示错误提示");
|
|
|
|
|
- // 使用AlertDialog显示错误提示
|
|
|
|
|
showCardLoginErrorDialog(data);
|
|
showCardLoginErrorDialog(data);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -148,6 +395,7 @@ Rectangle {
|
|
|
|
|
|
|
|
// 作业票请求状态标志
|
|
// 作业票请求状态标志
|
|
|
property bool isRequestingJobTickets: false
|
|
property bool isRequestingJobTickets: false
|
|
|
|
|
+ property string lastRequestedWorkNodeId: "" // 最近一次请求的 nodeId,408 刷新时重试用
|
|
|
HttpGetWorkNodeDetail {
|
|
HttpGetWorkNodeDetail {
|
|
|
id: httpGetWorkNodeDetail
|
|
id: httpGetWorkNodeDetail
|
|
|
}
|
|
}
|
|
@@ -159,6 +407,18 @@ Rectangle {
|
|
|
httpClientThread.signalResponseGetWorkNodeDetail.disconnect(httpGetWorkNodeDetail.slotHttpResponseGetWorkNodeDetail);
|
|
httpClientThread.signalResponseGetWorkNodeDetail.disconnect(httpGetWorkNodeDetail.slotHttpResponseGetWorkNodeDetail);
|
|
|
httpClientThread.signalResponseGetFormById.disconnect(httpGetWorkNodeDetail.slotHttpResponseGetFormById);
|
|
httpClientThread.signalResponseGetFormById.disconnect(httpGetWorkNodeDetail.slotHttpResponseGetFormById);
|
|
|
|
|
|
|
|
|
|
+ if (state === 408) {
|
|
|
|
|
+ control.cardLoginLoading = false;
|
|
|
|
|
+ control.showTimeoutDialog(function () {
|
|
|
|
|
+ control.cardLoginLoading = true;
|
|
|
|
|
+ httpGetWorkNodeDetail.nodeId = control.lastRequestedWorkNodeId;
|
|
|
|
|
+ httpGetWorkNodeDetail.signalGetRequestData.connect(httpClientThread.slotGetRequestData);
|
|
|
|
|
+ httpClientThread.signalResponseGetWorkNodeDetail.connect(httpGetWorkNodeDetail.slotHttpResponseGetWorkNodeDetail);
|
|
|
|
|
+ httpClientThread.signalResponseGetFormById.connect(httpGetWorkNodeDetail.slotHttpResponseGetFormById);
|
|
|
|
|
+ httpGetWorkNodeDetail.start();
|
|
|
|
|
+ });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
var isShowNegativeBtn = false;
|
|
var isShowNegativeBtn = false;
|
|
|
var isShowOpsitiveBtn = false;
|
|
var isShowOpsitiveBtn = false;
|
|
|
var textNegativeBtnStr = "";
|
|
var textNegativeBtnStr = "";
|
|
@@ -254,14 +514,21 @@ Rectangle {
|
|
|
Connections {
|
|
Connections {
|
|
|
target: appHttpGetJobTickets
|
|
target: appHttpGetJobTickets
|
|
|
function onSignalJobTicketsReturnStat(stat, msg) {
|
|
function onSignalJobTicketsReturnStat(stat, msg) {
|
|
|
- // 只有在本页面发起请求时才处理
|
|
|
|
|
- if (!isRequestingJobTickets) {
|
|
|
|
|
- console.log("[Login.qml] 忽略非本页面发起的作业票响应");
|
|
|
|
|
|
|
+ if (!isRequestingJobTickets)
|
|
|
return;
|
|
return;
|
|
|
- }
|
|
|
|
|
isRequestingJobTickets = false;
|
|
isRequestingJobTickets = false;
|
|
|
- console.log("[Login.qml] 作业票返回: stat=" + stat + ", msg=" + msg);
|
|
|
|
|
-
|
|
|
|
|
|
|
+ jobTicketsLoading = false;
|
|
|
|
|
+ cardLoginLoading = false;
|
|
|
|
|
+ passwordLoginLoading = false;
|
|
|
|
|
+
|
|
|
|
|
+ if (stat === 408) {
|
|
|
|
|
+ showTimeoutDialog(function () {
|
|
|
|
|
+ isRequestingJobTickets = true;
|
|
|
|
|
+ jobTicketsLoading = true;
|
|
|
|
|
+ appHttpGetJobTickets.start();
|
|
|
|
|
+ });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
if (stat !== 0 || JobTicketModel.rowCount() === 0) {
|
|
if (stat !== 0 || JobTicketModel.rowCount() === 0) {
|
|
|
appStackView.push("components/NoJobTicketDialog.qml");
|
|
appStackView.push("components/NoJobTicketDialog.qml");
|
|
|
} else {
|
|
} else {
|
|
@@ -274,7 +541,7 @@ Rectangle {
|
|
|
appStackView.push("JobTicketPage.qml");
|
|
appStackView.push("JobTicketPage.qml");
|
|
|
} else {
|
|
} else {
|
|
|
control.cardLoginLoading = true;
|
|
control.cardLoginLoading = true;
|
|
|
- cardLoginLoadingDialog.loadingText = "正在查询作业任务..."
|
|
|
|
|
|
|
+ control.lastRequestedWorkNodeId = jobTicketinfo.nodeId;
|
|
|
httpGetWorkNodeDetail.nodeId = jobTicketinfo.nodeId;
|
|
httpGetWorkNodeDetail.nodeId = jobTicketinfo.nodeId;
|
|
|
// 防止Repeater复制槽函数
|
|
// 防止Repeater复制槽函数
|
|
|
httpGetWorkNodeDetail.signalGetRequestData.connect(httpClientThread.slotGetRequestData);
|
|
httpGetWorkNodeDetail.signalGetRequestData.connect(httpClientThread.slotGetRequestData);
|
|
@@ -333,21 +600,28 @@ Rectangle {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- property Component faceInputDelegate: Rectangle {
|
|
|
|
|
|
|
+ property Component faceInputDelegate: Item {
|
|
|
id: faceInputRect
|
|
id: faceInputRect
|
|
|
width: control.width
|
|
width: control.width
|
|
|
height: control.height
|
|
height: control.height
|
|
|
- color: "transparent"
|
|
|
|
|
|
|
+ // 使用 Item 而非 Rectangle,避免透明在某些环境下被渲染成黑色方框,只保留圆形区域与控件
|
|
|
|
|
|
|
|
property int faceDiameter: Math.min(faceAreaSize, Math.min(width, height) * 0.6)
|
|
property int faceDiameter: Math.min(faceAreaSize, Math.min(width, height) * 0.6)
|
|
|
property int faceRadius: faceDiameter / 2
|
|
property int faceRadius: faceDiameter / 2
|
|
|
property real faceCx: width / 2
|
|
property real faceCx: width / 2
|
|
|
property real faceCy: height / 2
|
|
property real faceCy: height / 2
|
|
|
|
|
|
|
|
- // 关闭人脸登录弹窗
|
|
|
|
|
|
|
+ // 关闭人脸登录弹窗:停止采集、清空队列与缓存、释放资源
|
|
|
function closeFaceLoginDialog() {
|
|
function closeFaceLoginDialog() {
|
|
|
|
|
+ control.faceDialogJustOpened = false;
|
|
|
|
|
+ control.faceLoginError902Count = 0;
|
|
|
|
|
+ control.faceLoginError902Reached = false;
|
|
|
InteractiveFace.cameraImageStop();
|
|
InteractiveFace.cameraImageStop();
|
|
|
cameraTimeoutTimer.stop();
|
|
cameraTimeoutTimer.stop();
|
|
|
|
|
+ if (typeof httpFaceLogin !== "undefined" && httpFaceLogin.clearFaceQueue)
|
|
|
|
|
+ httpFaceLogin.clearFaceQueue();
|
|
|
|
|
+ if (parent.parent && parent.parent.clearFaceLoginResources)
|
|
|
|
|
+ parent.parent.clearFaceLoginResources();
|
|
|
loginInput.visible = false;
|
|
loginInput.visible = false;
|
|
|
loginInput.sourceComponent = null;
|
|
loginInput.sourceComponent = null;
|
|
|
control.forceActiveFocus();
|
|
control.forceActiveFocus();
|
|
@@ -363,21 +637,22 @@ Rectangle {
|
|
|
Component.onDestruction: {
|
|
Component.onDestruction: {
|
|
|
InteractiveFace.cameraImageStop();
|
|
InteractiveFace.cameraImageStop();
|
|
|
cameraTimeoutTimer.stop();
|
|
cameraTimeoutTimer.stop();
|
|
|
|
|
+ if (parent.parent && parent.parent.clearFaceLoginResources)
|
|
|
|
|
+ parent.parent.clearFaceLoginResources();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 60秒超时定时器
|
|
|
|
|
|
|
+ // 超时与释放:摄像头 60s 无操作自动关;人脸 API 单次 3s;成功提示 300ms 后关
|
|
|
Timer {
|
|
Timer {
|
|
|
id: cameraTimeoutTimer
|
|
id: cameraTimeoutTimer
|
|
|
- interval: 60000 // 60秒
|
|
|
|
|
|
|
+ interval: 60000
|
|
|
repeat: false
|
|
repeat: false
|
|
|
running: false
|
|
running: false
|
|
|
onTriggered: {
|
|
onTriggered: {
|
|
|
- console.log("[Login.qml] 摄像头打开60秒超时,自动关闭");
|
|
|
|
|
closeFaceLoginDialog();
|
|
closeFaceLoginDialog();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 圆心摄像头影像(圆形裁剪)
|
|
|
|
|
|
|
+ // 摄像头影像(圆形):参考之前可用的写法,OpacityMask 只裁 Image,Image 隐藏仅通过 OpacityMask 显示
|
|
|
Item {
|
|
Item {
|
|
|
id: cameraCircleArea
|
|
id: cameraCircleArea
|
|
|
width: faceDiameter
|
|
width: faceDiameter
|
|
@@ -386,40 +661,42 @@ Rectangle {
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
anchors.verticalCenterOffset: -30 // 向上偏移,为下方提示文字留出空间
|
|
anchors.verticalCenterOffset: -30 // 向上偏移,为下方提示文字留出空间
|
|
|
|
|
|
|
|
- // 圆形背景(当没有视频流时显示)
|
|
|
|
|
|
|
+ // 圆形背景(无视频流时显示)
|
|
|
Rectangle {
|
|
Rectangle {
|
|
|
id: cameraBackground
|
|
id: cameraBackground
|
|
|
anchors.fill: parent
|
|
anchors.fill: parent
|
|
|
radius: faceRadius
|
|
radius: faceRadius
|
|
|
- color: "#1A1A1A" // 深色背景
|
|
|
|
|
|
|
+ color: "#1A1A1A"
|
|
|
visible: !hasValidCameraImage || faceCameraUrl === ""
|
|
visible: !hasValidCameraImage || faceCameraUrl === ""
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 圆形遮罩(用于裁剪)
|
|
|
|
|
|
|
+ // 圆形遮罩(白=显示,透明=裁剪)
|
|
|
Rectangle {
|
|
Rectangle {
|
|
|
id: circleMask
|
|
id: circleMask
|
|
|
- anchors.fill: parent
|
|
|
|
|
|
|
+ width: faceDiameter
|
|
|
|
|
+ height: faceDiameter
|
|
|
radius: faceRadius
|
|
radius: faceRadius
|
|
|
color: "white"
|
|
color: "white"
|
|
|
visible: false
|
|
visible: false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 摄像头画面(圆形裁剪)
|
|
|
|
|
|
|
+ // 摄像头画面(圆形裁剪):只对 Image 做 OpacityMask,有图时才显示
|
|
|
OpacityMask {
|
|
OpacityMask {
|
|
|
anchors.fill: parent
|
|
anchors.fill: parent
|
|
|
source: cameraImage
|
|
source: cameraImage
|
|
|
maskSource: circleMask
|
|
maskSource: circleMask
|
|
|
visible: hasValidCameraImage && faceCameraUrl !== ""
|
|
visible: hasValidCameraImage && faceCameraUrl !== ""
|
|
|
|
|
+ cached: true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 摄像头画面源(隐藏,只用于 OpacityMask)
|
|
|
|
|
|
|
+ // 摄像头画面源(隐藏,只通过 OpacityMask 显示为圆形)
|
|
|
Image {
|
|
Image {
|
|
|
id: cameraImage
|
|
id: cameraImage
|
|
|
anchors.fill: parent
|
|
anchors.fill: parent
|
|
|
fillMode: Image.PreserveAspectCrop
|
|
fillMode: Image.PreserveAspectCrop
|
|
|
source: hasValidCameraImage ? faceCameraUrl : ""
|
|
source: hasValidCameraImage ? faceCameraUrl : ""
|
|
|
cache: false
|
|
cache: false
|
|
|
- visible: false // 隐藏,只通过 OpacityMask 显示
|
|
|
|
|
|
|
+ visible: false
|
|
|
asynchronous: false
|
|
asynchronous: false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -427,7 +704,7 @@ Rectangle {
|
|
|
Text {
|
|
Text {
|
|
|
id: noImageText
|
|
id: noImageText
|
|
|
anchors.centerIn: parent
|
|
anchors.centerIn: parent
|
|
|
- text: "无图像"
|
|
|
|
|
|
|
+ text: "视频加载中..."
|
|
|
color: "#888888"
|
|
color: "#888888"
|
|
|
font.pixelSize: 28
|
|
font.pixelSize: 28
|
|
|
font.bold: true
|
|
font.bold: true
|
|
@@ -435,114 +712,190 @@ Rectangle {
|
|
|
z: 2
|
|
z: 2
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 圆形识别框边框(仅保留圆形边框,无任何其他装饰或图案)
|
|
|
|
|
|
|
+ // 人脸检测框(绿色矩形,画在圆形视频上方)
|
|
|
|
|
+ Repeater {
|
|
|
|
|
+ model: faceInputRect.faceRectsList
|
|
|
|
|
+ delegate: Rectangle {
|
|
|
|
|
+ readonly property real imgW: faceInputRect.faceImageWidth > 0 ? faceInputRect.faceImageWidth : 1
|
|
|
|
|
+ readonly property real imgH: faceInputRect.faceImageHeight > 0 ? faceInputRect.faceImageHeight : 1
|
|
|
|
|
+ readonly property real scale: Math.max(cameraCircleArea.width / imgW, cameraCircleArea.height / imgH)
|
|
|
|
|
+ readonly property real visSize: cameraCircleArea.width / scale
|
|
|
|
|
+ readonly property real cropX: (imgW - visSize) / 2
|
|
|
|
|
+ readonly property real cropY: (imgH - visSize) / 2
|
|
|
|
|
+ x: (modelData && modelData.x !== undefined ? (modelData.x - cropX) * scale : 0)
|
|
|
|
|
+ y: (modelData && modelData.y !== undefined ? (modelData.y - cropY) * scale : 0)
|
|
|
|
|
+ width: (modelData && modelData.width !== undefined ? modelData.width * scale : 0)
|
|
|
|
|
+ height: (modelData && modelData.height !== undefined ? modelData.height * scale : 0)
|
|
|
|
|
+ color: "transparent"
|
|
|
|
|
+ border.color: "#00FF00"
|
|
|
|
|
+ border.width: 3
|
|
|
|
|
+ visible: faceInputRect.hasValidCameraImage && faceInputRect.faceImageWidth > 0 && width > 0 && height > 0
|
|
|
|
|
+ antialiasing: true
|
|
|
|
|
+ z: 3
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 红色圆框
|
|
|
Rectangle {
|
|
Rectangle {
|
|
|
- id: faceRecognitionFrame
|
|
|
|
|
anchors.fill: parent
|
|
anchors.fill: parent
|
|
|
radius: faceRadius
|
|
radius: faceRadius
|
|
|
color: "transparent"
|
|
color: "transparent"
|
|
|
- border.color: "#40C7FF" // 使用主题色
|
|
|
|
|
|
|
+ border.color: "red"
|
|
|
border.width: 3
|
|
border.width: 3
|
|
|
- z: 3
|
|
|
|
|
antialiasing: true
|
|
antialiasing: true
|
|
|
|
|
+ z: 4
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- // 提示文字
|
|
|
|
|
|
|
+ // 提示文字(无人脸/不完整 / 多张人脸 / 单人脸)
|
|
|
Text {
|
|
Text {
|
|
|
id: faceTipText
|
|
id: faceTipText
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
|
anchors.top: cameraCircleArea.bottom
|
|
anchors.top: cameraCircleArea.bottom
|
|
|
anchors.topMargin: 20
|
|
anchors.topMargin: 20
|
|
|
- text: "请将脸部放入框内"
|
|
|
|
|
|
|
+ text: faceTipMessage
|
|
|
color: "#FFFFFF"
|
|
color: "#FFFFFF"
|
|
|
font.pixelSize: 24
|
|
font.pixelSize: 24
|
|
|
font.bold: true
|
|
font.bold: true
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
- z: 10 // 确保显示在遮罩上方
|
|
|
|
|
|
|
+ z: 10
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
property string faceCameraUrl: ""
|
|
property string faceCameraUrl: ""
|
|
|
property bool hasValidCameraImage: false
|
|
property bool hasValidCameraImage: false
|
|
|
-
|
|
|
|
|
|
|
+ property int faceCount: 0
|
|
|
|
|
+ property var faceRectsList: []
|
|
|
|
|
+ property int faceImageWidth: 0
|
|
|
|
|
+ property int faceImageHeight: 0
|
|
|
|
|
+ property string faceTipMessage: "请看向摄像头,确保人脸在圆框内"
|
|
|
|
|
+ property bool faceCanCapture: false
|
|
|
|
|
+
|
|
|
Timer {
|
|
Timer {
|
|
|
id: faceCameraRefreshTimer
|
|
id: faceCameraRefreshTimer
|
|
|
interval: 100
|
|
interval: 100
|
|
|
repeat: true
|
|
repeat: true
|
|
|
- running: faceInputRect.visible && InteractiveFace.hasCamera()
|
|
|
|
|
|
|
+ running: (faceInputRect.visible || faceInputRect.width > 0) && InteractiveFace.hasCamera()
|
|
|
onTriggered: {
|
|
onTriggered: {
|
|
|
- // 先检查是否有有效图像
|
|
|
|
|
var hasImage = InteractiveFace.hasValidImage();
|
|
var hasImage = InteractiveFace.hasValidImage();
|
|
|
if (hasImage) {
|
|
if (hasImage) {
|
|
|
- // 只有在有有效图像时才更新 URL
|
|
|
|
|
faceInputRect.faceCameraUrl = InteractiveFace.imageUrl();
|
|
faceInputRect.faceCameraUrl = InteractiveFace.imageUrl();
|
|
|
faceInputRect.hasValidCameraImage = true;
|
|
faceInputRect.hasValidCameraImage = true;
|
|
|
|
|
+ faceInputRect.faceCount = InteractiveFace.faceCount();
|
|
|
|
|
+ faceInputRect.faceRectsList = InteractiveFace.faceRects();
|
|
|
|
|
+ faceInputRect.faceImageWidth = InteractiveFace.imageWidth();
|
|
|
|
|
+ faceInputRect.faceImageHeight = InteractiveFace.imageHeight();
|
|
|
|
|
+ faceInputRect.faceCanCapture = InteractiveFace.canCaptureFaceForLogin();
|
|
|
|
|
+ // 动态提示:无人脸 / 人脸不正或不完整 / 人脸完整
|
|
|
|
|
+ if (faceInputRect.faceCount === 0) {
|
|
|
|
|
+ faceInputRect.faceTipMessage = "请看向摄像头,确保人脸在圆框内";
|
|
|
|
|
+ } else if (faceInputRect.faceCount > 1) {
|
|
|
|
|
+ faceInputRect.faceTipMessage = "检测到多张人脸,请保持框内仅有一个人脸";
|
|
|
|
|
+ } else if (!faceInputRect.faceCanCapture) {
|
|
|
|
|
+ faceInputRect.faceTipMessage = "请正对着摄像头,确保人脸完全显示";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ faceInputRect.faceTipMessage = "正在识别...";
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
- // 没有图像时清空 URL,避免请求空图像
|
|
|
|
|
if (faceInputRect.hasValidCameraImage) {
|
|
if (faceInputRect.hasValidCameraImage) {
|
|
|
faceInputRect.faceCameraUrl = "";
|
|
faceInputRect.faceCameraUrl = "";
|
|
|
}
|
|
}
|
|
|
faceInputRect.hasValidCameraImage = false;
|
|
faceInputRect.hasValidCameraImage = false;
|
|
|
|
|
+ faceInputRect.faceCount = 0;
|
|
|
|
|
+ faceInputRect.faceRectsList = [];
|
|
|
|
|
+ faceInputRect.faceImageWidth = 0;
|
|
|
|
|
+ faceInputRect.faceImageHeight = 0;
|
|
|
|
|
+ faceInputRect.faceCanCapture = false;
|
|
|
|
|
+ faceInputRect.faceTipMessage = "请看向摄像头,确保人脸在圆框内";
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 半透明遮罩 + 圆形镂空(圆心区域透明,显示摄像头)
|
|
|
|
|
- Shape {
|
|
|
|
|
- anchors.fill: parent
|
|
|
|
|
- ShapePath {
|
|
|
|
|
- fillRule: ShapePath.OddEvenFill
|
|
|
|
|
- strokeWidth: 0
|
|
|
|
|
- fillColor: "#99000000"
|
|
|
|
|
- startX: 0
|
|
|
|
|
- startY: 0
|
|
|
|
|
-
|
|
|
|
|
- PathLine { x: faceInputRect.width; y: 0 }
|
|
|
|
|
- PathLine { x: faceInputRect.width; y: faceInputRect.height }
|
|
|
|
|
- PathLine { x: 0; y: faceInputRect.height }
|
|
|
|
|
- PathLine { x: 0; y: 0 }
|
|
|
|
|
- // 内圈:圆心圆形镂空
|
|
|
|
|
- PathMove { x: faceInputRect.faceCx - faceInputRect.faceRadius; y: faceInputRect.faceCy }
|
|
|
|
|
- PathArc {
|
|
|
|
|
- x: faceInputRect.faceCx; y: faceInputRect.faceCy + faceInputRect.faceRadius
|
|
|
|
|
- radiusX: faceInputRect.faceRadius; radiusY: faceInputRect.faceRadius
|
|
|
|
|
- direction: PathArc.Clockwise
|
|
|
|
|
- }
|
|
|
|
|
- PathArc {
|
|
|
|
|
- x: faceInputRect.faceCx + faceInputRect.faceRadius; y: faceInputRect.faceCy
|
|
|
|
|
- radiusX: faceInputRect.faceRadius; radiusY: faceInputRect.faceRadius
|
|
|
|
|
- direction: PathArc.Clockwise
|
|
|
|
|
- }
|
|
|
|
|
- PathArc {
|
|
|
|
|
- x: faceInputRect.faceCx; y: faceInputRect.faceCy - faceInputRect.faceRadius
|
|
|
|
|
- radiusX: faceInputRect.faceRadius; radiusY: faceInputRect.faceRadius
|
|
|
|
|
- direction: PathArc.Clockwise
|
|
|
|
|
- }
|
|
|
|
|
- PathArc {
|
|
|
|
|
- x: faceInputRect.faceCx - faceInputRect.faceRadius; y: faceInputRect.faceCy
|
|
|
|
|
- radiusX: faceInputRect.faceRadius; radiusY: faceInputRect.faceRadius
|
|
|
|
|
- direction: PathArc.Clockwise
|
|
|
|
|
|
|
+ // 流程:检测到一张人脸(排除多张) → 截取合格则入队(最多10张FIFO) → 每300ms从队列随机取一张发API(超时3s);取比推略快故队列常为0~2
|
|
|
|
|
+ // 入队:仅当检测到恰好一张人脸且可截取时,每 150ms 截一帧入队(满足「一张人脸即入队」)
|
|
|
|
|
+ Timer {
|
|
|
|
|
+ id: faceLoginTriggerTimer
|
|
|
|
|
+ interval: 150
|
|
|
|
|
+ repeat: false
|
|
|
|
|
+ running: false
|
|
|
|
|
+ onTriggered: {
|
|
|
|
|
+ if (!faceInputRect.faceCanCapture) return;
|
|
|
|
|
+ var data = InteractiveFace.captureFaceImageForLogin();
|
|
|
|
|
+ var dataLen = (data && data.byteLength !== undefined) ? data.byteLength : (data && typeof data.size === "function" ? data.size() : (data ? (data.length || 0) : 0));
|
|
|
|
|
+ if (data && dataLen > 0) {
|
|
|
|
|
+ httpFaceLogin.pushFaceImage(data);
|
|
|
|
|
+ if (faceInputRect.faceCanCapture && !faceLoginTriggerTimer.running)
|
|
|
|
|
+ faceLoginTriggerTimer.start();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ // 定时请求 API:每 300ms 启动一次请求;登录成功后或 902 已达 5 次时停止
|
|
|
|
|
+ Timer {
|
|
|
|
|
+ id: faceLoginApiTimer
|
|
|
|
|
+ interval: 300
|
|
|
|
|
+ repeat: true
|
|
|
|
|
+ running: faceInputRect.visible && InteractiveFace.hasCamera()
|
|
|
|
|
+ && !control.faceLoginSuccessPending && !control.faceLoginError902Reached
|
|
|
|
|
+ onTriggered: {
|
|
|
|
|
+ httpFaceLogin.start();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ function checkFaceLoginTrigger() {
|
|
|
|
|
+ var pending = control.faceLoginSuccessPending || control.faceLoginError902Reached;
|
|
|
|
|
+ if (faceCanCapture && !faceLoginTriggerTimer.running && !pending)
|
|
|
|
|
+ faceLoginTriggerTimer.start();
|
|
|
|
|
+ else
|
|
|
|
|
+ faceLoginTriggerTimer.stop();
|
|
|
|
|
+ }
|
|
|
|
|
+ onFaceCanCaptureChanged: checkFaceLoginTrigger()
|
|
|
|
|
+ // 902 达 5 次时立即停止采集定时器
|
|
|
|
|
+ Connections {
|
|
|
|
|
+ target: control
|
|
|
|
|
+ function onFaceLoginError902ReachedChanged() {
|
|
|
|
|
+ if (control.faceLoginError902Reached)
|
|
|
|
|
+ faceLoginTriggerTimer.stop();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 关闭按钮
|
|
|
|
|
- MButton {
|
|
|
|
|
|
|
+ // 关闭按钮:圆形框右上角,圆形图标按钮
|
|
|
|
|
+ Rectangle {
|
|
|
id: faceCloseBtn
|
|
id: faceCloseBtn
|
|
|
- anchors.right: parent.right
|
|
|
|
|
- anchors.rightMargin: 80
|
|
|
|
|
- anchors.top: parent.top
|
|
|
|
|
- anchors.topMargin: 60
|
|
|
|
|
- width: 120
|
|
|
|
|
- height: 52
|
|
|
|
|
- text: "关闭"
|
|
|
|
|
- buttonColor: "#CC0F1929"
|
|
|
|
|
- textColor: "white"
|
|
|
|
|
- iconCharacter: "\uf00d"
|
|
|
|
|
- iconColor: "#40a9ff"
|
|
|
|
|
- onClicked: {
|
|
|
|
|
- closeFaceLoginDialog();
|
|
|
|
|
|
|
+ anchors.right: cameraCircleArea.right
|
|
|
|
|
+ anchors.top: cameraCircleArea.top
|
|
|
|
|
+ anchors.rightMargin: -4
|
|
|
|
|
+ anchors.topMargin: -4
|
|
|
|
|
+ width: 44
|
|
|
|
|
+ height: 44
|
|
|
|
|
+ radius: width / 2
|
|
|
|
|
+ color: faceCloseBtnMa.containsMouse ? "#80000000" : "#40000000"
|
|
|
|
|
+ border.width: 1
|
|
|
|
|
+ border.color: faceCloseBtnMa.containsMouse ? "#60ffffff" : "#30ffffff"
|
|
|
|
|
+ opacity: faceCloseBtnMa.containsMouse ? 1 : 0.9
|
|
|
|
|
+ z: 20
|
|
|
|
|
+
|
|
|
|
|
+ Behavior on color { ColorAnimation { duration: 120 } }
|
|
|
|
|
+ Behavior on border.color { ColorAnimation { duration: 120 } }
|
|
|
|
|
+ Behavior on opacity { NumberAnimation { duration: 120 } }
|
|
|
|
|
+
|
|
|
|
|
+ Text {
|
|
|
|
|
+ width: parent.width
|
|
|
|
|
+ height: parent.height
|
|
|
|
|
+ anchors.horizontalCenter: parent.horizontalCenter
|
|
|
|
|
+ anchors.verticalCenter: parent.verticalCenter
|
|
|
|
|
+ anchors.verticalCenterOffset: -3
|
|
|
|
|
+ text: "×"
|
|
|
|
|
+ font.pixelSize: 36
|
|
|
|
|
+ font.bold: true
|
|
|
|
|
+ color: "#ffffff"
|
|
|
|
|
+ verticalAlignment: Text.AlignVCenter
|
|
|
|
|
+ horizontalAlignment: Text.AlignHCenter
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ MouseArea {
|
|
|
|
|
+ id: faceCloseBtnMa
|
|
|
|
|
+ anchors.fill: parent
|
|
|
|
|
+ hoverEnabled: true
|
|
|
|
|
+ cursorShape: Qt.PointingHandCursor
|
|
|
|
|
+ onClicked: closeFaceLoginDialog()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -703,6 +1056,7 @@ Rectangle {
|
|
|
|
|
|
|
|
onClicked: {
|
|
onClicked: {
|
|
|
virtualKeyboard.hide();
|
|
virtualKeyboard.hide();
|
|
|
|
|
+ passwordLoginLoading = true;
|
|
|
httpPasswordLogin.username = usernameText.text;
|
|
httpPasswordLogin.username = usernameText.text;
|
|
|
httpPasswordLogin.password = passwordText.text;
|
|
httpPasswordLogin.password = passwordText.text;
|
|
|
httpPasswordLogin.start();
|
|
httpPasswordLogin.start();
|
|
@@ -848,7 +1202,14 @@ Rectangle {
|
|
|
textColor: "white"
|
|
textColor: "white"
|
|
|
|
|
|
|
|
onClicked: {
|
|
onClicked: {
|
|
|
- // 弹出人脸遮罩
|
|
|
|
|
|
|
+ faceLoginSuccessPending = false;
|
|
|
|
|
+ showFaceLoginSuccessPrompt = false;
|
|
|
|
|
+ faceLoginError902Count = 0;
|
|
|
|
|
+ faceLoginError902Reached = false;
|
|
|
|
|
+ if (typeof httpFaceLogin !== "undefined" && httpFaceLogin.clearFaceQueue)
|
|
|
|
|
+ httpFaceLogin.clearFaceQueue();
|
|
|
|
|
+ faceDialogJustOpened = true;
|
|
|
|
|
+ faceDialogJustOpenedTimer.start();
|
|
|
loginInput.sourceComponent = faceInputDelegate;
|
|
loginInput.sourceComponent = faceInputDelegate;
|
|
|
loginInput.visible = true;
|
|
loginInput.visible = true;
|
|
|
}
|
|
}
|
|
@@ -906,7 +1267,7 @@ Rectangle {
|
|
|
width: 320
|
|
width: 320
|
|
|
height: 28
|
|
height: 28
|
|
|
|
|
|
|
|
- text: qsTr("您可以通过刷卡直接进行登录")
|
|
|
|
|
|
|
+ text: qsTr("请选择登录方式,您可以通过刷卡直接登录")
|
|
|
color: loginTextColor
|
|
color: loginTextColor
|
|
|
font.pixelSize: 20
|
|
font.pixelSize: 20
|
|
|
// 普通文字不使用图标字体
|
|
// 普通文字不使用图标字体
|
|
@@ -942,21 +1303,22 @@ Rectangle {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- // 登录成功
|
|
|
|
|
|
|
+ // 登录成功(刷卡/密码/人脸统一:按刷卡成功逻辑跳转作业票)
|
|
|
onLoginSuccessChanged: {
|
|
onLoginSuccessChanged: {
|
|
|
if (loginSuccess) {
|
|
if (loginSuccess) {
|
|
|
- // 关闭可能存在的loading和错误提示
|
|
|
|
|
|
|
+ // 关闭可能存在的 loading、刷卡错误、人脸错误、账号密码错误弹窗
|
|
|
cardLoginLoading = false;
|
|
cardLoginLoading = false;
|
|
|
|
|
+ passwordLoginLoading = false;
|
|
|
closeCardLoginErrorDialog();
|
|
closeCardLoginErrorDialog();
|
|
|
-
|
|
|
|
|
|
|
+ closeFaceLoginErrorDialog();
|
|
|
|
|
+ closePasswordLoginErrorDialog();
|
|
|
|
|
+
|
|
|
loginInput.visible = false;
|
|
loginInput.visible = false;
|
|
|
- appShowLogout = true
|
|
|
|
|
|
|
+ appShowLogout = true;
|
|
|
loginInput.sourceComponent = null;
|
|
loginInput.sourceComponent = null;
|
|
|
- // TODO: 登录成功跳入作业页面
|
|
|
|
|
- // appStackView.push("WorkingPage.qml")
|
|
|
|
|
isRequestingJobTickets = true;
|
|
isRequestingJobTickets = true;
|
|
|
- httpGetJobTickets.start();
|
|
|
|
|
- //appStackView.push("components/NoJobTicketDialog.qml")
|
|
|
|
|
|
|
+ jobTicketsLoading = true;
|
|
|
|
|
+ appHttpGetJobTickets.start();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -972,10 +1334,63 @@ Rectangle {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 刷卡登录Loading组件
|
|
|
|
|
|
|
+ // 账号密码登录错误提示自动关闭定时器(3秒)
|
|
|
|
|
+ Timer {
|
|
|
|
|
+ id: passwordLoginErrorAutoCloseTimer
|
|
|
|
|
+ interval: 3000
|
|
|
|
|
+ running: false
|
|
|
|
|
+ repeat: false
|
|
|
|
|
+ onTriggered: {
|
|
|
|
|
+ closePasswordLoginErrorDialog();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 超时弹窗 10 秒倒计时定时器
|
|
|
|
|
+ Timer {
|
|
|
|
|
+ id: timeoutAutoCloseTimer
|
|
|
|
|
+ interval: 1000
|
|
|
|
|
+ repeat: true
|
|
|
|
|
+ running: false
|
|
|
|
|
+ onTriggered: {
|
|
|
|
|
+ timeoutCountdown--;
|
|
|
|
|
+ if (timeoutDialog !== null)
|
|
|
|
|
+ timeoutDialog.confirmBtnText = "确认(" + Math.max(0, timeoutCountdown) + ")";
|
|
|
|
|
+ if (timeoutCountdown <= 0) {
|
|
|
|
|
+ if (typeof timeoutRefreshCallback === "function")
|
|
|
|
|
+ timeoutRefreshCallback();
|
|
|
|
|
+ closeTimeoutDialog();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 刷卡/账号密码/作业票 Loading
|
|
|
LoadingDialog {
|
|
LoadingDialog {
|
|
|
id: cardLoginLoadingDialog
|
|
id: cardLoginLoadingDialog
|
|
|
- loadingText: "登录中,请稍后..."
|
|
|
|
|
- showLoading: cardLoginLoading
|
|
|
|
|
|
|
+ loadingText: jobTicketsLoading ? "正在查询作业任务..." : (passwordLoginLoading ? "处理中..." : "登录中,请稍后...")
|
|
|
|
|
+ showLoading: cardLoginLoading || passwordLoginLoading || jobTicketsLoading
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 人脸登录成功提示:无全屏黑底,仅居中卡片+文字;先关人脸弹窗再关提示,避免黑闪
|
|
|
|
|
+ Item {
|
|
|
|
|
+ id: faceLoginSuccessOverlay
|
|
|
|
|
+ anchors.fill: parent
|
|
|
|
|
+ visible: showFaceLoginSuccessPrompt
|
|
|
|
|
+ z: 2000
|
|
|
|
|
+ Rectangle {
|
|
|
|
|
+ id: faceSuccessCard
|
|
|
|
|
+ anchors.centerIn: parent
|
|
|
|
|
+ width: faceSuccessLabel.implicitWidth + 48
|
|
|
|
|
+ height: faceSuccessLabel.implicitHeight + 24
|
|
|
|
|
+ radius: 12
|
|
|
|
|
+ color: "#E6181890"
|
|
|
|
|
+ Text {
|
|
|
|
|
+ id: faceSuccessLabel
|
|
|
|
|
+ anchors.centerIn: parent
|
|
|
|
|
+ text: (faceLoginSuccessNickName || "用户") + "、登录成功"
|
|
|
|
|
+ color: "white"
|
|
|
|
|
+ font.pixelSize: 36
|
|
|
|
|
+ font.bold: true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|