From cbb00d4729243e4949b3c921fc2f94cb03ca8aaa Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期五, 27 三月 2026 18:47:43 +0800
Subject: [PATCH] #

---
 src/main/webapp/static/js/login/login.js |  275 +++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 245 insertions(+), 30 deletions(-)

diff --git a/src/main/webapp/static/js/login/login.js b/src/main/webapp/static/js/login/login.js
index b12ff13..2fb1a1b 100644
--- a/src/main/webapp/static/js/login/login.js
+++ b/src/main/webapp/static/js/login/login.js
@@ -11,15 +11,25 @@
                 localeOptions: [],
                 currentLocale: "zh-CN",
                 loginLoading: false,
+                passkeyLoading: false,
+                mfaLoading: false,
                 toolsDialogVisible: false,
                 textDialogVisible: false,
                 uploadDialogVisible: false,
+                mfaDialogVisible: false,
                 licenseBase64: "",
                 titleClickCount: 0,
                 titleClickTimer: null,
                 loginForm: {
                     mobile: "",
                     password: ""
+                },
+                mfaForm: {
+                    code: ""
+                },
+                mfaPending: {
+                    ticket: "",
+                    username: ""
                 },
                 textDialog: {
                     title: "",
@@ -34,6 +44,21 @@
                     password: [
                         { required: true, message: "璇疯緭鍏ュ瘑鐮�", trigger: "blur" }
                     ]
+                },
+                mfaRules: {
+                    code: [
+                        { required: true, message: "璇疯緭鍏�6浣嶉獙璇佺爜", trigger: "blur" },
+                        {
+                            validator: function (rule, value, callback) {
+                                if (!/^\d{6}$/.test(String(value || "").trim())) {
+                                    callback(new Error("璇疯緭鍏�6浣嶆暟瀛楅獙璇佺爜"));
+                                    return;
+                                }
+                                callback();
+                            },
+                            trigger: "blur"
+                        }
+                    ]
                 }
             };
         },
@@ -45,19 +70,50 @@
                 var localeTick = this.localeTick;
                 void localeTick;
                 if (window.WCS_I18N && typeof window.WCS_I18N.t === "function") {
-                    return window.WCS_I18N.t(key);
+                    var translated = window.WCS_I18N.t(key);
+                    if (translated && translated !== key) {
+                        return translated;
+                    }
                 }
                 return fallback || key;
+            },
+            refreshRuleMessages: function () {
+                var vm = this;
+                vm.loginRules = {
+                    mobile: [
+                        { required: true, message: vm.text("login.validation.usernameRequired", "璇疯緭鍏ヨ处鍙�"), trigger: "blur" }
+                    ],
+                    password: [
+                        { required: true, message: vm.text("login.validation.passwordRequired", "璇疯緭鍏ュ瘑鐮�"), trigger: "blur" }
+                    ]
+                };
+                vm.mfaRules = {
+                    code: [
+                        { required: true, message: vm.text("login.validation.mfaRequired", "璇疯緭鍏�6浣嶉獙璇佺爜"), trigger: "blur" },
+                        {
+                            validator: function (rule, value, callback) {
+                                if (!/^\d{6}$/.test(String(value || "").trim())) {
+                                    callback(new Error(vm.text("login.validation.mfaInvalid", "璇疯緭鍏�6浣嶆暟瀛楅獙璇佺爜")));
+                                    return;
+                                }
+                                callback();
+                            },
+                            trigger: "blur"
+                        }
+                    ]
+                };
             },
             initLanguageSwitch: function () {
                 var vm = this;
                 if (!window.WCS_I18N || typeof window.WCS_I18N.onReady !== "function") {
+                    vm.refreshRuleMessages();
                     return;
                 }
                 window.WCS_I18N.onReady(function (i18n) {
                     vm.localeOptions = i18n.getLocaleOptions();
                     vm.currentLocale = i18n.getLocale();
                     document.title = i18n.t("login.title");
+                    vm.refreshRuleMessages();
                     vm.localeTick++;
                 });
             },
@@ -97,6 +153,33 @@
                     return true;
                 });
             },
+            handlePasskeyLogin: function () {
+                var vm = this;
+                if (!window.WCS_WEBAUTHN || !window.WCS_WEBAUTHN.isSupported()) {
+                    vm.$message.error(vm.resolvePasskeyErrorMessage({ message: window.isSecureContext ? "not-supported" : "secure-context" }, "login.error.passkeyOptionsFailed", "鑾峰彇閫氳瀵嗛挜鐧诲綍鍙傛暟澶辫触"));
+                    return;
+                }
+                vm.passkeyLoading = true;
+                ajaxJson({
+                    url: baseUrl + "/login/passkey/options.action",
+                    data: {
+                        mobile: vm.loginForm.mobile
+                    },
+                    method: "POST",
+                    success: function (res) {
+                        if (Number(res.code) !== 200) {
+                            vm.passkeyLoading = false;
+                            vm.$message.error(res.msg || vm.text("login.error.passkeyOptionsFailed", "鑾峰彇閫氳瀵嗛挜鐧诲綍鍙傛暟澶辫触"));
+                            return;
+                        }
+                        vm.executePasskeyLogin(res.data || {});
+                    },
+                    error: function () {
+                        vm.passkeyLoading = false;
+                        vm.$message.error(vm.text("login.error.passkeyOptionsFailed", "鑾峰彇閫氳瀵嗛挜鐧诲綍鍙傛暟澶辫触"));
+                    }
+                });
+            },
             submitLogin: function () {
                 var vm = this;
                 vm.loginLoading = true;
@@ -108,21 +191,143 @@
                     },
                     method: "POST",
                     success: function (res) {
+                        var payload = res && res.data ? res.data : {};
                         if (Number(res.code) === 200) {
-                            localStorage.setItem("token", res.data.token);
-                            localStorage.setItem("username", res.data.username);
-                            window.location.href = "index.html";
-                            return;
-                        }
-                        vm.$message.error(res.msg || "鐧诲綍澶辫触");
+                            if (payload.mfaRequired) {
+                                vm.openMfaDialog(payload);
+                                return;
+                            }
+                        vm.finishLogin(payload);
+                        return;
+                    }
+                        vm.$message.error(res.msg || vm.text("login.error.loginFailed", "鐧诲綍澶辫触"));
                     },
                     error: function () {
-                        vm.$message.error("鐧诲綍澶辫触");
+                        vm.$message.error(vm.text("login.error.loginFailed", "鐧诲綍澶辫触"));
                     },
                     complete: function () {
                         vm.loginLoading = false;
                     }
                 });
+            },
+            openMfaDialog: function (payload) {
+                this.mfaPending = {
+                    ticket: payload.mfaTicket || "",
+                    username: payload.username || this.loginForm.mobile || ""
+                };
+                this.mfaForm.code = "";
+                this.mfaDialogVisible = true;
+                if (this.$refs.mfaForm) {
+                    this.$nextTick(function () {
+                        this.$refs.mfaForm.clearValidate();
+                    });
+                }
+            },
+            closeMfaDialog: function () {
+                this.mfaDialogVisible = false;
+                this.mfaLoading = false;
+                this.mfaPending = {
+                    ticket: "",
+                    username: ""
+                };
+                this.mfaForm.code = "";
+                if (this.$refs.mfaForm) {
+                    this.$refs.mfaForm.clearValidate();
+                }
+            },
+            handleMfaLogin: function () {
+                var vm = this;
+                if (!vm.$refs.mfaForm) {
+                    return;
+                }
+                vm.$refs.mfaForm.validate(function (valid) {
+                    if (!valid) {
+                        return false;
+                    }
+                    vm.submitMfaLogin();
+                    return true;
+                });
+            },
+            submitMfaLogin: function () {
+                var vm = this;
+                if (!vm.mfaPending.ticket) {
+                    vm.$message.error(vm.text("login.error.mfaTicketExpired", "鐧诲綍绁ㄦ嵁宸插け鏁堬紝璇烽噸鏂扮櫥褰�"));
+                    vm.closeMfaDialog();
+                    return;
+                }
+                vm.mfaLoading = true;
+                ajaxJson({
+                    url: baseUrl + "/login/mfa.action",
+                    data: {
+                        ticket: vm.mfaPending.ticket,
+                        code: vm.mfaForm.code
+                    },
+                    method: "POST",
+                    success: function (res) {
+                        if (Number(res.code) === 200) {
+                            vm.finishLogin(res.data || {});
+                            return;
+                        }
+                        vm.$message.error(res.msg || vm.text("login.error.mfaFailed", "楠岃瘉澶辫触"));
+                    },
+                    error: function () {
+                        vm.$message.error(vm.text("login.error.mfaFailed", "楠岃瘉澶辫触"));
+                    },
+                    complete: function () {
+                        vm.mfaLoading = false;
+                    }
+                });
+            },
+            executePasskeyLogin: function (payload) {
+                var vm = this;
+                window.WCS_WEBAUTHN.authenticate(payload).then(function (assertion) {
+                    ajaxJson({
+                        url: baseUrl + "/login/passkey/verify.action",
+                        data: {
+                            ticket: payload.ticket,
+                            credentialId: assertion.credentialId,
+                            clientDataJSON: assertion.clientDataJSON,
+                            authenticatorData: assertion.authenticatorData,
+                            signature: assertion.signature
+                        },
+                        method: "POST",
+                        success: function (res) {
+                            if (Number(res.code) === 200) {
+                                vm.finishLogin(res.data || {});
+                                return;
+                            }
+                            vm.$message.error(res.msg || vm.text("login.error.passkeyVerifyFailed", "閫氳瀵嗛挜楠岃瘉澶辫触"));
+                        },
+                        error: function () {
+                            vm.$message.error(vm.text("login.error.passkeyVerifyFailed", "閫氳瀵嗛挜楠岃瘉澶辫触"));
+                        },
+                        complete: function () {
+                            vm.passkeyLoading = false;
+                        }
+                    });
+                }).catch(function (err) {
+                    vm.passkeyLoading = false;
+                    vm.$message.error(vm.resolvePasskeyErrorMessage(err, "login.error.passkeyVerifyFailed", "閫氳瀵嗛挜楠岃瘉澶辫触"));
+                });
+            },
+            finishLogin: function (payload) {
+                localStorage.setItem("token", payload.token || "");
+                localStorage.setItem("username", payload.username || this.loginForm.mobile || "");
+                this.closeMfaDialog();
+                window.location.href = "index.html";
+            },
+            resolvePasskeyErrorMessage: function (err, fallbackKey, fallbackText) {
+                var message = err && err.message ? String(err.message) : "";
+                if (message === "secure-context") {
+                    return this.text("login.passkey.secureContext", "閫氳瀵嗛挜浠呮敮鎸佸湪 HTTPS 鎴� localhost 鐜涓嬩娇鐢�");
+                }
+                if (message === "not-supported" || message === "extension-unsupported" || message === "public-key-missing") {
+                    return this.text("login.passkey.browserUnsupported", "褰撳墠娴忚鍣ㄤ笉鏀寔閫氳瀵嗛挜锛岃浣跨敤鏈�鏂扮増 Chrome銆丒dge 鎴� Safari");
+                }
+                if (err && err.name === "NotAllowedError") {
+                    return this.text(fallbackKey, fallbackText);
+                }
+                return this.text(fallbackKey, fallbackText);
             },
             openTextDialog: function (title, label, text, tip) {
                 var pretty = "";
@@ -149,9 +354,9 @@
                     textarea.select();
                     document.execCommand("copy");
                     document.body.removeChild(textarea);
-                    this.$message.success("宸插鍒跺埌鍓创鏉�");
+                    this.$message.success(this.text("login.dialog.copied", "宸插鍒跺埌鍓创鏉�"));
                 } catch (err) {
-                    this.$message.error("澶嶅埗澶辫触");
+                    this.$message.error(this.text("login.dialog.copyFailed", "澶嶅埗澶辫触"));
                 }
             },
             copyText: function () {
@@ -159,7 +364,7 @@
                 var text = vm.textDialog.text || "";
                 if (navigator.clipboard && navigator.clipboard.writeText) {
                     navigator.clipboard.writeText(text).then(function () {
-                        vm.$message.success("宸插鍒跺埌鍓创鏉�");
+                        vm.$message.success(vm.text("login.dialog.copied", "宸插鍒跺埌鍓创鏉�"));
                     }).catch(function () {
                         vm.fallbackCopy(text);
                     });
@@ -174,13 +379,18 @@
                     method: "GET",
                     success: function (res) {
                         if (Number(res.code) === 200) {
-                            vm.openTextDialog("鑾峰彇璇锋眰鐮�", "璇锋眰鐮�", res.msg || "", "璇锋眰鐮佷腑宸插寘鍚」鐩悕绉帮紝鐩存帴鍙戠粰璁稿彲璇佹湇鍔$鍗冲彲銆�");
+                            vm.openTextDialog(
+                                vm.text("login.requestCode.title", "鑾峰彇璇锋眰鐮�"),
+                                vm.text("login.requestCode.label", "璇锋眰鐮�"),
+                                res.msg || "",
+                                vm.text("login.requestCode.tip", "璇锋眰鐮佷腑宸插寘鍚」鐩悕绉帮紝鐩存帴鍙戠粰璁稿彲璇佹湇鍔$鍗冲彲銆�")
+                            );
                             return;
                         }
-                        vm.$message.error(res.msg || "鑾峰彇璇锋眰鐮佸け璐�");
+                        vm.$message.error(res.msg || vm.text("login.error.requestCodeFailed", "鑾峰彇璇锋眰鐮佸け璐�"));
                     },
                     error: function () {
-                        vm.$message.error("鑾峰彇璇锋眰鐮佸け璐�");
+                        vm.$message.error(vm.text("login.error.requestCodeFailed", "鑾峰彇璇锋眰鐮佸け璐�"));
                     }
                 });
             },
@@ -190,17 +400,22 @@
                     url: baseUrl + "/license/getServerInfos",
                     method: "GET",
                     success: function (res) {
-                        vm.openTextDialog("鑾峰彇绯荤粺閰嶇疆", "绯荤粺閰嶇疆淇℃伅", res, "鑰侀」鐩粛鍙户缁娇鐢ㄨ繖浠界‖浠朵俊鎭� JSON 鐢宠璁稿彲璇併��");
+                        vm.openTextDialog(
+                            vm.text("login.serverInfo.title", "鑾峰彇绯荤粺閰嶇疆"),
+                            vm.text("login.serverInfo.label", "绯荤粺閰嶇疆淇℃伅"),
+                            res,
+                            vm.text("login.serverInfo.tip", "鑰侀」鐩粛鍙户缁娇鐢ㄨ繖浠界‖浠朵俊鎭� JSON 鐢宠璁稿彲璇併��")
+                        );
                     },
                     error: function () {
-                        vm.$message.error("鑾峰彇绯荤粺閰嶇疆淇℃伅澶辫触");
+                        vm.$message.error(vm.text("login.error.serverInfoFailed", "鑾峰彇绯荤粺閰嶇疆淇℃伅澶辫触"));
                     }
                 });
             },
             submitLicense: function () {
                 var vm = this;
                 if (!vm.licenseBase64) {
-                    vm.$message.warning("璁稿彲璇佸唴瀹逛笉鑳戒负绌�");
+                    vm.$message.warning(vm.text("login.error.licenseEmpty", "璁稿彲璇佸唴瀹逛笉鑳戒负绌�"));
                     return;
                 }
                 ajaxJson({
@@ -212,35 +427,35 @@
                         if (Number(res.code) === 200) {
                             vm.uploadDialogVisible = false;
                             vm.licenseBase64 = "";
-                            vm.$message.success("璁稿彲璇佹洿鏂版垚鍔�");
+                            vm.$message.success(vm.text("login.license.success", "璁稿彲璇佹洿鏂版垚鍔�"));
                             return;
                         }
-                        vm.$message.error(res.msg || "璁稿彲璇佹洿鏂板け璐�");
+                        vm.$message.error(res.msg || vm.text("login.error.licenseUpdateFailed", "璁稿彲璇佹洿鏂板け璐�"));
                     },
                     error: function () {
-                        vm.$message.error("璁稿彲璇佸綍鍏ュけ璐�");
+                        vm.$message.error(vm.text("login.error.licenseImportFailed", "璁稿彲璇佸綍鍏ュけ璐�"));
                     }
                 });
             },
             activateLicense: function () {
                 var vm = this;
-                vm.$confirm("纭畾鎵ц涓�閿縺娲诲悧锛�", "鎻愮ず", {
+                vm.$confirm(vm.text("login.activate.confirm", "纭畾鎵ц涓�閿縺娲诲悧锛�"), vm.text("common.prompt", "鎻愮ず"), {
                     type: "warning",
-                    confirmButtonText: "纭畾",
-                    cancelButtonText: "鍙栨秷"
+                    confirmButtonText: vm.text("common.ok", "纭畾"),
+                    cancelButtonText: vm.text("login.mfa.cancel", "鍙栨秷")
                 }).then(function () {
                     ajaxJson({
                         url: baseUrl + "/license/activate",
                         method: "POST",
                         success: function (res) {
                             if (Number(res.code) === 200) {
-                                vm.$message.success("婵�娲绘垚鍔�");
+                                vm.$message.success(vm.text("login.activate.success", "婵�娲绘垚鍔�"));
                                 return;
                             }
-                            vm.$message.error(res.msg || "婵�娲诲け璐�");
+                            vm.$message.error(res.msg || vm.text("login.activate.failed", "婵�娲诲け璐�"));
                         },
                         error: function () {
-                            vm.$message.error("婵�娲诲け璐�");
+                            vm.$message.error(vm.text("login.activate.failed", "婵�娲诲け璐�"));
                         }
                     });
                 }).catch(function () {
@@ -253,15 +468,15 @@
                     method: "GET",
                     success: function (res) {
                         if (Number(res.code) === 200) {
-                            vm.$alert(res.msg || "", "椤圭洰鍚嶇О", {
-                                confirmButtonText: "纭畾"
+                            vm.$alert(res.msg || "", vm.text("login.projectName.title", "椤圭洰鍚嶇О"), {
+                                confirmButtonText: vm.text("common.ok", "纭畾")
                             });
                             return;
                         }
-                        vm.$message.error(res.msg || "鑾峰彇椤圭洰鍚嶇О澶辫触");
+                        vm.$message.error(res.msg || vm.text("login.error.projectNameFailed", "鑾峰彇椤圭洰鍚嶇О澶辫触"));
                     },
                     error: function () {
-                        vm.$message.error("鑾峰彇椤圭洰鍚嶇О澶辫触");
+                        vm.$message.error(vm.text("login.error.projectNameFailed", "鑾峰彇椤圭洰鍚嶇О澶辫触"));
                     }
                 });
             }

--
Gitblit v1.9.1