(function () { function ajaxJson(options) { $.ajax(options); } new Vue({ el: "#app", data: function () { return { localeTick: 0, 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: "", label: "", text: "", tip: "" }, loginRules: { mobile: [ { required: true, message: "请输入账号", trigger: "blur" } ], 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" } ] } }; }, created: function () { this.initLanguageSwitch(); }, methods: { text: function (key, fallback) { var localeTick = this.localeTick; void localeTick; if (window.WCS_I18N && typeof window.WCS_I18N.t === "function") { 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++; }); }, handleLocaleChange: function () { var vm = this; if (!window.WCS_I18N || typeof window.WCS_I18N.setLocale !== "function") { return; } window.WCS_I18N.setLocale(vm.currentLocale); setTimeout(function () { document.title = vm.text("login.title", "系统登录"); vm.localeTick++; }, 0); }, handleTitleClick: function () { var vm = this; vm.titleClickCount++; if (vm.titleClickTimer) { clearTimeout(vm.titleClickTimer); } if (vm.titleClickCount >= 3) { vm.titleClickCount = 0; vm.toolsDialogVisible = true; return; } vm.titleClickTimer = setTimeout(function () { vm.titleClickCount = 0; }, 500); }, handleLogin: function () { var vm = this; vm.$refs.loginForm.validate(function (valid) { if (!valid) { return false; } vm.submitLogin(); 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; ajaxJson({ url: baseUrl + "/login.action", data: { mobile: vm.loginForm.mobile, password: hex_md5(vm.loginForm.password) }, method: "POST", success: function (res) { var payload = res && res.data ? res.data : {}; if (Number(res.code) === 200) { 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.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、Edge 或 Safari"); } if (err && err.name === "NotAllowedError") { return this.text(fallbackKey, fallbackText); } return this.text(fallbackKey, fallbackText); }, openTextDialog: function (title, label, text, tip) { var pretty = ""; try { pretty = typeof text === "string" ? text : JSON.stringify(text, null, 2); } catch (e) { pretty = String(text || ""); } this.textDialog = { title: title, label: label, text: pretty, tip: tip || "" }; this.textDialogVisible = true; }, fallbackCopy: function (text) { try { var textarea = document.createElement("textarea"); textarea.value = text; textarea.style.position = "fixed"; textarea.style.opacity = "0"; document.body.appendChild(textarea); textarea.select(); document.execCommand("copy"); document.body.removeChild(textarea); this.$message.success(this.text("login.dialog.copied", "已复制到剪贴板")); } catch (err) { this.$message.error(this.text("login.dialog.copyFailed", "复制失败")); } }, copyText: function () { var vm = this; var text = vm.textDialog.text || ""; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(function () { vm.$message.success(vm.text("login.dialog.copied", "已复制到剪贴板")); }).catch(function () { vm.fallbackCopy(text); }); return; } vm.fallbackCopy(text); }, requestCode: function () { var vm = this; ajaxJson({ url: baseUrl + "/license/getRequestCode", method: "GET", success: function (res) { if (Number(res.code) === 200) { 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.text("login.error.requestCodeFailed", "获取请求码失败")); }, error: function () { vm.$message.error(vm.text("login.error.requestCodeFailed", "获取请求码失败")); } }); }, getServerInfo: function () { var vm = this; ajaxJson({ url: baseUrl + "/license/getServerInfos", method: "GET", success: function (res) { 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.text("login.error.serverInfoFailed", "获取系统配置信息失败")); } }); }, submitLicense: function () { var vm = this; if (!vm.licenseBase64) { vm.$message.warning(vm.text("login.error.licenseEmpty", "许可证内容不能为空")); return; } ajaxJson({ url: baseUrl + "/license/updateLicense", method: "POST", contentType: "application/json;charset=UTF-8", data: JSON.stringify({ license: vm.licenseBase64 }), success: function (res) { if (Number(res.code) === 200) { vm.uploadDialogVisible = false; vm.licenseBase64 = ""; vm.$message.success(vm.text("login.license.success", "许可证更新成功")); return; } vm.$message.error(res.msg || vm.text("login.error.licenseUpdateFailed", "许可证更新失败")); }, error: function () { vm.$message.error(vm.text("login.error.licenseImportFailed", "许可证录入失败")); } }); }, activateLicense: function () { var vm = this; vm.$confirm(vm.text("login.activate.confirm", "确定执行一键激活吗?"), vm.text("common.prompt", "提示"), { type: "warning", 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.text("login.activate.success", "激活成功")); return; } vm.$message.error(res.msg || vm.text("login.activate.failed", "激活失败")); }, error: function () { vm.$message.error(vm.text("login.activate.failed", "激活失败")); } }); }).catch(function () { }); }, getProjectName: function () { var vm = this; ajaxJson({ url: baseUrl + "/license/getProjectName", method: "GET", success: function (res) { if (Number(res.code) === 200) { vm.$alert(res.msg || "", vm.text("login.projectName.title", "项目名称"), { confirmButtonText: vm.text("common.ok", "确定") }); return; } vm.$message.error(res.msg || vm.text("login.error.projectNameFailed", "获取项目名称失败")); }, error: function () { vm.$message.error(vm.text("login.error.projectNameFailed", "获取项目名称失败")); } }); } } }); })();