更新说明:\n1. 新增用户端与管理员端 Passkey 登录/注册/设备管理(最多3台,支持设备备注、删除设备)。\n2. 修复 Passkey 注册与登录流程中的浏览器/证书/CSRF相关问题,增强错误提示。\n3. 前台登录页改为独立入口,首屏仅加载必要资源,其他页面按需加载。\n4. 系统配置页改为静默获取金山文档状态,避免首屏阻塞,并优化状态展示为“检测中/已登录/未登录/异常”。\n5. 补充后端接口与页面渲染适配,修复多入口下样式依赖注入问题。\n6. 同步更新前后台构建产物与相关静态资源。
2 lines
12 KiB
JavaScript
2 lines
12 KiB
JavaScript
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};
|