From bd6b518aae61608ddc2d82b43ccc283dc95b9c54 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期三, 11 三月 2026 13:59:33 +0800
Subject: [PATCH] #

---
 src/main/webapp/static/js/detail/detail.js |  177 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 176 insertions(+), 1 deletions(-)

diff --git a/src/main/webapp/static/js/detail/detail.js b/src/main/webapp/static/js/detail/detail.js
index 0e041d7..5a80ca2 100644
--- a/src/main/webapp/static/js/detail/detail.js
+++ b/src/main/webapp/static/js/detail/detail.js
@@ -19,6 +19,9 @@
                 mfaDialogMode: "enable",
                 mfaSetupLoading: false,
                 mfaSubmitting: false,
+                passkeyDialogVisible: false,
+                passkeyDialogMode: "register",
+                passkeySubmitting: false,
                 form: {
                     id: "",
                     roleName: "",
@@ -30,7 +33,12 @@
                     mfaEnabled: 0,
                     mfaEnabled$: "鍚�",
                     mfaBoundTime$: "",
-                    mfaMaskedSecret: ""
+                    mfaMaskedSecret: "",
+                    passkeyBound: false,
+                    passkeyName: "",
+                    passkeyBoundTime$: "",
+                    passkeyLastUsedTime$: "",
+                    passkeyTransports: ""
                 },
                 passwordForm: {
                     oldPassword: "",
@@ -45,6 +53,10 @@
                     secret: "",
                     qrCode: "",
                     otpAuth: ""
+                },
+                passkeyForm: {
+                    name: "",
+                    currentPassword: ""
                 },
                 rules: {
                     username: [
@@ -109,6 +121,11 @@
                             },
                             trigger: "blur"
                         }
+                    ]
+                },
+                passkeyRules: {
+                    currentPassword: [
+                        { required: true, message: "璇疯緭鍏ュ綋鍓嶅瘑鐮�", trigger: "blur" }
                     ]
                 }
             };
@@ -314,6 +331,164 @@
                     }
                 });
             },
+            openPasskeyRegisterDialog: function () {
+                if (!window.WCS_WEBAUTHN || !window.WCS_WEBAUTHN.isSupported()) {
+                    this.$message.error(this.resolvePasskeyErrorMessage({ message: window.isSecureContext ? "not-supported" : "secure-context" }, "褰撳墠鐜涓嶆敮鎸侀�氳瀵嗛挜"));
+                    return;
+                }
+                this.passkeyDialogMode = "register";
+                this.resetPasskeyDialogState();
+                this.passkeyDialogVisible = true;
+            },
+            openPasskeyRemoveDialog: function () {
+                this.passkeyDialogMode = "remove";
+                this.resetPasskeyDialogState();
+                this.passkeyDialogVisible = true;
+            },
+            closePasskeyDialog: function () {
+                this.passkeyDialogVisible = false;
+                this.resetPasskeyDialogState();
+            },
+            resetPasskeyDialogState: function () {
+                this.passkeySubmitting = false;
+                this.passkeyForm = {
+                    name: "",
+                    currentPassword: ""
+                };
+                if (this.$refs.passkeyForm) {
+                    this.$refs.passkeyForm.clearValidate();
+                }
+            },
+            handlePasskeySubmit: function () {
+                var vm = this;
+                if (!vm.$refs.passkeyForm) {
+                    return;
+                }
+                vm.$refs.passkeyForm.validate(function (valid) {
+                    if (!valid) {
+                        return false;
+                    }
+                    if (vm.passkeyDialogMode === "register") {
+                        vm.submitPasskeyRegister();
+                    } else {
+                        vm.submitPasskeyRemove();
+                    }
+                    return true;
+                });
+            },
+            submitPasskeyRegister: function () {
+                var vm = this;
+                if (!window.WCS_WEBAUTHN || !window.WCS_WEBAUTHN.isSupported()) {
+                    vm.$message.error(vm.resolvePasskeyErrorMessage({ message: window.isSecureContext ? "not-supported" : "secure-context" }, "褰撳墠鐜涓嶆敮鎸侀�氳瀵嗛挜"));
+                    return;
+                }
+                vm.passkeySubmitting = true;
+                $.ajax({
+                    url: baseUrl + "/user/passkey/register/options/auth",
+                    headers: { token: localStorage.getItem("token") },
+                    method: "POST",
+                    success: function (res) {
+                        if (handleForbidden(res)) {
+                            return;
+                        }
+                        if (Number(res.code) !== 200) {
+                            vm.passkeySubmitting = false;
+                            vm.$message.error(res.msg || "閫氳瀵嗛挜閰嶇疆鍔犺浇澶辫触");
+                            return;
+                        }
+                        vm.executePasskeyRegister(res.data || {});
+                    },
+                    error: function () {
+                        vm.passkeySubmitting = false;
+                        vm.$message.error("閫氳瀵嗛挜閰嶇疆鍔犺浇澶辫触");
+                    }
+                });
+            },
+            executePasskeyRegister: function (optionsPayload) {
+                var vm = this;
+                window.WCS_WEBAUTHN.register(optionsPayload).then(function (credential) {
+                    $.ajax({
+                        url: baseUrl + "/user/passkey/register/finish/auth",
+                        headers: { token: localStorage.getItem("token") },
+                        data: {
+                            ticket: optionsPayload.ticket,
+                            currentPassword: hex_md5(vm.passkeyForm.currentPassword),
+                            name: vm.passkeyForm.name,
+                            credentialId: credential.credentialId,
+                            clientDataJSON: credential.clientDataJSON,
+                            authenticatorData: credential.authenticatorData,
+                            publicKey: credential.publicKey,
+                            publicKeyAlgorithm: credential.publicKeyAlgorithm,
+                            transports: credential.transports
+                        },
+                        method: "POST",
+                        success: function (res) {
+                            if (handleForbidden(res)) {
+                                return;
+                            }
+                            if (Number(res.code) !== 200) {
+                                vm.$message.error(res.msg || "閫氳瀵嗛挜缁戝畾澶辫触");
+                                return;
+                            }
+                            vm.$message.success("閫氳瀵嗛挜宸茬粦瀹�");
+                            vm.closePasskeyDialog();
+                            vm.loadDetail();
+                        },
+                        error: function () {
+                            vm.$message.error("閫氳瀵嗛挜缁戝畾澶辫触");
+                        },
+                        complete: function () {
+                            vm.passkeySubmitting = false;
+                        }
+                    });
+                }).catch(function (err) {
+                    vm.passkeySubmitting = false;
+                    vm.$message.error(vm.resolvePasskeyErrorMessage(err, "閫氳瀵嗛挜缁戝畾澶辫触"));
+                });
+            },
+            submitPasskeyRemove: function () {
+                var vm = this;
+                vm.passkeySubmitting = true;
+                $.ajax({
+                    url: baseUrl + "/user/passkey/remove/auth",
+                    headers: { token: localStorage.getItem("token") },
+                    data: {
+                        currentPassword: hex_md5(vm.passkeyForm.currentPassword)
+                    },
+                    method: "POST",
+                    success: function (res) {
+                        if (handleForbidden(res)) {
+                            return;
+                        }
+                        if (Number(res.code) !== 200) {
+                            vm.$message.error(res.msg || "閫氳瀵嗛挜瑙g粦澶辫触");
+                            return;
+                        }
+                        vm.$message.success("閫氳瀵嗛挜宸茶В缁�");
+                        vm.closePasskeyDialog();
+                        vm.loadDetail();
+                    },
+                    error: function () {
+                        vm.$message.error("閫氳瀵嗛挜瑙g粦澶辫触");
+                    },
+                    complete: function () {
+                        vm.passkeySubmitting = false;
+                    }
+                });
+            },
+            resolvePasskeyErrorMessage: function (err, fallback) {
+                var message = err && err.message ? String(err.message) : "";
+                if (message === "secure-context") {
+                    return "閫氳瀵嗛挜浠呮敮鎸佸湪 HTTPS 鎴� localhost 鐜涓嬩娇鐢�";
+                }
+                if (message === "not-supported" || message === "extension-unsupported" || message === "public-key-missing") {
+                    return "褰撳墠娴忚鍣ㄤ笉鏀寔閫氳瀵嗛挜锛岃浣跨敤鏈�鏂扮増 Chrome銆丒dge 鎴� Safari";
+                }
+                if (err && err.name === "NotAllowedError") {
+                    return "宸插彇娑堥�氳瀵嗛挜鎿嶄綔鎴栭獙璇佽秴鏃�";
+                }
+                return fallback || "閫氳瀵嗛挜鎿嶄綔澶辫触";
+            },
             copySecret: function () {
                 var vm = this;
                 var text = vm.mfaSetup.secret || "";

--
Gitblit v1.9.1