Files
zsglpt/static/app/assets/LoginPage-D5iXLq7p.js
yuyx 7007f5f6f5 feat: 完成 Passkey 能力与前后台加载优化
更新说明:\n1. 新增用户端与管理员端 Passkey 登录/注册/设备管理(最多3台,支持设备备注、删除设备)。\n2. 修复 Passkey 注册与登录流程中的浏览器/证书/CSRF相关问题,增强错误提示。\n3. 前台登录页改为独立入口,首屏仅加载必要资源,其他页面按需加载。\n4. 系统配置页改为静默获取金山文档状态,避免首屏阻塞,并优化状态展示为“检测中/已登录/未登录/异常”。\n5. 补充后端接口与页面渲染适配,修复多入口下样式依赖注入问题。\n6. 同步更新前后台构建产物与相关静态资源。
2026-02-15 23:51:46 +08:00

2 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import{f as W,g as o,h as Ce,i as _e,j as r,k as s,l as u,n as Z,t as m,w as b,v as w,m as ee,p as te,o as i}from"./vendor-vue-DxN60LNb.js";import{_ as Se}from"./style-CEbARg1o.js";const Pe={class:"login-page"},Te={class:"login-container"},xe={class:"form-group"},Oe={class:"form-group"},Ae={key:1,class:"form-group"},Ee={class:"captcha-row"},Ue=["src"],Re=["disabled"],Ve=["disabled"],Le={class:"action-links"},Ne={class:"modal-card"},$e={class:"modal-head"},Ke={key:0,class:"modal-tip warn"},De={key:1,class:"modal-tip error"},Be={class:"form-group"},Ie={class:"form-group"},He={class:"captcha-row"},je=["src"],Fe={class:"modal-actions"},ze=["disabled"],Je={class:"modal-card"},We={class:"modal-head"},qe={key:0,class:"modal-tip error"},Me={class:"form-group"},Ge={class:"form-group"},Xe={class:"captcha-row"},Qe=["src"],Ye={class:"modal-actions"},Ze=["disabled"],et={__name:"LoginPage",setup(tt){const l=W({username:"",password:"",captcha:""}),k=o(!1),A=o(""),B=o(""),E=o(!1),U=o(!1),C=o(!1),I=o(!1),H=o(""),R=o(""),_=o(!1),S=o(!1),d=W({username:"",captcha:""}),V=o(""),j=o(""),L=o(!1),T=o(""),v=o(""),p=W({email:"",captcha:""}),N=o(""),F=o(""),$=o(!1),y=o(""),ae=Ce(()=>!0),q=o(!1);function se(t){const e=String(t).replace(/([.*+?^${}()|[\]\\])/g,"\\$1"),a=document.cookie.match(new RegExp(`(?:^|; )${e}=([^;]*)`));return a?decodeURIComponent(a[1]):""}class ne extends Error{constructor(e,a,n){super(e||"请求失败"),this.name="ApiError",this.response={status:Number(a||0),data:n||{}}}}async function h(t,e={}){const a=String(e.method||"GET").toUpperCase(),n={...e.headers||{}},P=Object.prototype.hasOwnProperty.call(e,"body");if(P&&!n["Content-Type"]&&(n["Content-Type"]="application/json"),!["GET","HEAD","OPTIONS"].includes(a)){const Y=se("csrf_token");Y&&(n["X-CSRF-Token"]=Y)}const g=await fetch(`/api${t}`,{method:a,headers:n,credentials:"include",body:P?JSON.stringify(e.body??{}):void 0});let c={};try{c=await g.json()}catch{c={}}if(!g.ok)throw new ne(c?.error||c?.message||`请求失败 (${g.status})`,g.status,c);return c}const oe=()=>h("/email/verify-status"),z=()=>h("/generate_captcha",{method:"POST",body:{}}),le=t=>h("/login",{method:"POST",body:t||{}}),re=t=>h("/passkeys/login/options",{method:"POST",body:t||{}}),ie=t=>h("/passkeys/login/verify",{method:"POST",body:t||{}}),ce=t=>h("/resend-verify-email",{method:"POST",body:t||{}}),ue=t=>h("/forgot-password",{method:"POST",body:t||{}});function M(t){const e=String(t||""),a="=".repeat((4-e.length%4)%4),n=(e+a).replace(/-/g,"+").replace(/_/g,"/"),P=window.atob(n),g=new Uint8Array(P.length);for(let c=0;c<P.length;c+=1)g[c]=P.charCodeAt(c);return g}function x(t){const e=t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t||[]);let a="";for(let n=0;n<e.length;n+=1)a+=String.fromCharCode(e[n]);return window.btoa(a).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function de(t){if(!t||typeof t!="object")throw new Error("Passkey参数无效");return t.publicKey&&typeof t.publicKey=="object"?t.publicKey:t}function pe(t){const e=de(t),a={...e,challenge:M(e.challenge)};return Array.isArray(e.allowCredentials)&&(a.allowCredentials=e.allowCredentials.map(n=>({...n,id:M(n.id)}))),a}function fe(t){const e=t?.response||{},a={id:t?.id,rawId:x(t?.rawId),type:t?.type,authenticatorAttachment:t?.authenticatorAttachment||void 0,response:{}};return e.clientDataJSON&&(a.response.clientDataJSON=x(e.clientDataJSON)),e.authenticatorData&&(a.response.authenticatorData=x(e.authenticatorData)),e.signature&&(a.response.signature=x(e.signature)),e.userHandle?a.response.userHandle=x(e.userHandle):a.response.userHandle=null,a}function me(){return typeof window<"u"&&window.isSecureContext&&!!window.PublicKeyCredential&&!!navigator.credentials}async function ve(t){const e=pe(t),a=await navigator.credentials.get({publicKey:e});return fe(a)}async function G(){if(!q.value)try{const t=await oe();C.value=!!t?.email_enabled,I.value=!!t?.register_verify_enabled}catch{C.value=!1,I.value=!1}finally{q.value=!0}}function f(t,e){H.value=String(t||""),R.value=String(e||"")}function X(){H.value="",R.value=""}async function O(){try{const t=await z();B.value=t?.session_id||"",A.value=t?.captcha_image||"",l.captcha=""}catch{B.value="",A.value=""}}async function K(){try{const t=await z();j.value=t?.session_id||"",V.value=t?.captcha_image||"",d.captcha=""}catch{j.value="",V.value=""}}async function D(){try{const t=await z();F.value=t?.session_id||"",N.value=t?.captcha_image||"",p.captcha=""}catch{F.value="",N.value=""}}function Q(){const t=new URLSearchParams(window.location.search||""),e=String(t.get("next")||"").trim(),a=e&&e.startsWith("/")&&!e.startsWith("//")&&!e.startsWith("/\\")?e:"";window.setTimeout(()=>{window.location.href=a||"/app"},300)}async function J(){if(X(),!l.username.trim()||!l.password.trim()){f("error","用户名和密码不能为空");return}if(k.value&&!l.captcha.trim()){f("error","请输入验证码");return}E.value=!0;try{const t=l.username.trim();await le({username:t,password:l.password,captcha_session:B.value,captcha:l.captcha.trim(),need_captcha:k.value}),f("success","登录成功,正在跳转..."),Q()}catch(t){const e=t?.response?.status,a=t?.response?.data,n=a?.error||a?.message||"登录失败";f("error",n),a?.need_captcha?(k.value=!0,await O()):k.value&&e===400&&await O()}finally{E.value=!1}}async function ye(){X();const t=l.username.trim();if(!me()){f("error","当前浏览器或环境不支持Passkey需 HTTPS");return}U.value=!0;try{const e=await re(t?{username:t}:{}),a=await ve(e?.publicKey||{});await ie(t?{username:t,credential:a}:{credential:a}),f("success","Passkey 登录成功,正在跳转..."),Q()}catch(e){const n=e?.response?.data?.error||(e?.name==="NotAllowedError"?"Passkey验证未完成可能取消、超时或设备未响应":e?.message||"Passkey登录失败");f("error",n)}finally{U.value=!1}}async function he(){await G(),_.value=!0,T.value="",v.value="",d.username="",d.captcha="",await K()}async function ge(){if(v.value="",T.value="",!C.value){v.value="邮件功能未启用,请联系管理员重置密码。";return}const t=d.username.trim();if(!t){v.value="请输入用户名";return}if(!d.captcha.trim()){v.value="请输入验证码";return}L.value=!0;try{const e=await ue({username:t,captcha_session:j.value,captcha:d.captcha.trim()});f("success",e?.message||"已发送重置邮件"),_.value=!1}catch(e){const a=e?.response?.data,n=a?.error||"发送失败";a?.code==="email_not_bound"?T.value=n:v.value=n,await K()}finally{L.value=!1}}async function be(){if(await G(),!I.value){f("error","当前未启用注册邮箱验证,无需重发验证邮件。");return}S.value=!0,p.email="",p.captcha="",y.value="",await D()}async function we(){y.value="";const t=p.email.trim();if(!t){y.value="请输入邮箱";return}if(!p.captcha.trim()){y.value="请输入验证码";return}$.value=!0;try{const e=await ce({email:t,captcha_session:F.value,captcha:p.captcha.trim()});f("success",e?.message||"验证邮件已发送,请查收"),S.value=!1}catch(e){const a=e?.response?.data;y.value=a?.error||"发送失败",await D()}finally{$.value=!1}}function ke(){window.location.href="/register"}return _e(async()=>{k.value&&await O()}),(t,e)=>(i(),r("div",Pe,[s("div",Te,[e[17]||(e[17]=s("div",{class:"login-header"},[s("span",{class:"login-badge"},"用户登录"),s("h1",null,"用户登录系统"),s("p",null,"知识管理平台")],-1)),R.value?(i(),r("div",{key:0,class:Z(["notice",H.value==="success"?"is-success":"is-error"])},m(R.value),3)):u("",!0),s("div",xe,[e[13]||(e[13]=s("label",{for:"username"},"用户账号",-1)),b(s("input",{id:"username","onUpdate:modelValue":e[0]||(e[0]=a=>l.username=a),class:"text-input",placeholder:"请输入用户名",autocomplete:"username"},null,512),[[w,l.username]])]),s("div",Oe,[e[14]||(e[14]=s("label",{for:"password"},"密码",-1)),b(s("input",{id:"password","onUpdate:modelValue":e[1]||(e[1]=a=>l.password=a),class:"text-input",type:"password",placeholder:"请输入密码",autocomplete:"current-password",onKeyup:ee(J,["enter"])},null,544),[[w,l.password]])]),k.value?(i(),r("div",Ae,[e[15]||(e[15]=s("label",{for:"captcha"},"验证码",-1)),s("div",Ee,[b(s("input",{id:"captcha","onUpdate:modelValue":e[2]||(e[2]=a=>l.captcha=a),class:"text-input captcha-input",placeholder:"请输入验证码",onKeyup:ee(J,["enter"])},null,544),[[w,l.captcha]]),A.value?(i(),r("img",{key:0,class:"captcha-img",src:A.value,alt:"验证码",title:"点击刷新",onClick:O},null,8,Ue)):u("",!0),s("button",{type:"button",class:"captcha-refresh",onClick:O},"刷新")])])):u("",!0),s("button",{type:"button",class:"btn-login",disabled:E.value,onClick:J},m(E.value?"登录中...":"登录系统"),9,Re),s("button",{type:"button",class:"btn-passkey",disabled:U.value,onClick:ye},m(U.value?"Passkey验证中...":"使用 Passkey 登录"),9,Ve),s("div",Le,[s("button",{type:"button",class:"link-btn",onClick:he},"忘记密码?"),ae.value?(i(),r("button",{key:0,type:"button",class:"link-btn",onClick:be},"重发验证邮件")):u("",!0)]),s("div",{class:"register-row"},[e[16]||(e[16]=s("span",null,"还没有账号?",-1)),s("button",{type:"button",class:"link-btn",onClick:ke},"立即注册")])]),_.value?(i(),r("div",{key:0,class:"modal-mask",onClick:e[7]||(e[7]=te(a=>_.value=!1,["self"]))},[s("section",Ne,[s("div",$e,[e[18]||(e[18]=s("h3",null,"找回密码",-1)),s("button",{type:"button",class:"modal-close",onClick:e[3]||(e[3]=a=>_.value=!1)},"关闭")]),s("p",{class:Z(["modal-tip",{warn:!C.value}])},m(C.value?"输入用户名并完成验证码,我们将向该账号绑定的邮箱发送重置链接。":"邮件功能未启用,无法通过邮箱找回密码。"),3),T.value?(i(),r("p",Ke,m(T.value),1)):u("",!0),v.value?(i(),r("p",De,m(v.value),1)):u("",!0),s("div",Be,[e[19]||(e[19]=s("label",{for:"forgot-username"},"用户名",-1)),b(s("input",{id:"forgot-username","onUpdate:modelValue":e[4]||(e[4]=a=>d.username=a),class:"text-input",placeholder:"请输入用户名"},null,512),[[w,d.username]])]),s("div",Ie,[e[20]||(e[20]=s("label",{for:"forgot-captcha"},"验证码",-1)),s("div",He,[b(s("input",{id:"forgot-captcha","onUpdate:modelValue":e[5]||(e[5]=a=>d.captcha=a),class:"text-input captcha-input",placeholder:"请输入验证码"},null,512),[[w,d.captcha]]),V.value?(i(),r("img",{key:0,class:"captcha-img",src:V.value,alt:"验证码",title:"点击刷新",onClick:K},null,8,je)):u("",!0),s("button",{type:"button",class:"captcha-refresh",onClick:K},"刷新")])]),s("div",Fe,[s("button",{type:"button",class:"btn-ghost",onClick:e[6]||(e[6]=a=>_.value=!1)},"取消"),s("button",{type:"button",class:"btn-login",disabled:L.value||!C.value,onClick:ge},m(L.value?"发送中...":"发送重置邮件"),9,ze)])])])):u("",!0),S.value?(i(),r("div",{key:1,class:"modal-mask",onClick:e[12]||(e[12]=te(a=>S.value=!1,["self"]))},[s("section",Je,[s("div",We,[e[21]||(e[21]=s("h3",null,"重发验证邮件",-1)),s("button",{type:"button",class:"modal-close",onClick:e[8]||(e[8]=a=>S.value=!1)},"关闭")]),e[24]||(e[24]=s("p",{class:"modal-tip"},"用于注册邮箱验证:请输入邮箱并完成验证码。",-1)),y.value?(i(),r("p",qe,m(y.value),1)):u("",!0),s("div",Me,[e[22]||(e[22]=s("label",{for:"resend-email"},"邮箱",-1)),b(s("input",{id:"resend-email","onUpdate:modelValue":e[9]||(e[9]=a=>p.email=a),class:"text-input",placeholder:"name@example.com"},null,512),[[w,p.email]])]),s("div",Ge,[e[23]||(e[23]=s("label",{for:"resend-captcha"},"验证码",-1)),s("div",Xe,[b(s("input",{id:"resend-captcha","onUpdate:modelValue":e[10]||(e[10]=a=>p.captcha=a),class:"text-input captcha-input",placeholder:"请输入验证码"},null,512),[[w,p.captcha]]),N.value?(i(),r("img",{key:0,class:"captcha-img",src:N.value,alt:"验证码",title:"点击刷新",onClick:D},null,8,Qe)):u("",!0),s("button",{type:"button",class:"captcha-refresh",onClick:D},"刷新")])]),s("div",Ye,[s("button",{type:"button",class:"btn-ghost",onClick:e[11]||(e[11]=a=>S.value=!1)},"取消"),s("button",{type:"button",class:"btn-login",disabled:$.value,onClick:we},m($.value?"发送中...":"发送"),9,Ze)])])])):u("",!0)]))}},nt=Se(et,[["__scopeId","data-v-01524203"]]);export{nt as default};