Files
zsglpt/static/admin/assets/SettingsPage-za1oQElD.js
yuyx 7627885b1b fix(passkey): 修复安卓端 Credential Manager 异常并增强兼容
更新说明:\n1. 优化 Passkey 注册参数(residentKey/hints),提升安卓设备兼容性。\n2. 前台与后台统一增强 Passkey 错误提示,针对 NotReadableError/小米浏览器给出明确引导。\n3. 同步更新相关前端页面逻辑与构建产物。
2026-02-16 00:17:11 +08:00

2 lines
9.8 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{a as v,_ as L}from"./index-BwP1dZnj.js";import{a as u,E as O}from"./vendor-element-B5S5pUKo.js";import{r as p,o as Y,aj as m,ap as Z,n as N,q as C,t as b,L as r,E as o,I as S,F as Q,D as K}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.js";async function W(t){const{data:e}=await v.put("/admin/username",{new_username:t});return e}async function ee(t={}){const e=String(t.currentPassword||""),n=String(t.newPassword||""),{data:l}=await v.put("/admin/password",{current_password:e,new_password:n});return l}async function te(){const{data:t}=await v.post("/logout");return t}async function ae(){const{data:t}=await v.get("/admin/passkeys");return t}async function $(t={}){const{data:e}=await v.post("/admin/passkeys/register/options",t);return e}async function ne(t={}){const{data:e}=await v.post("/admin/passkeys/register/verify",t);return e}async function se(t){const{data:e}=await v.delete(`/admin/passkeys/${t}`);return e}async function re(t={}){const{data:e}=await v.post("/admin/passkeys/client-error",t);return e}function le(t){if(!t||typeof t!="object")throw new Error("Passkey参数无效");return t.publicKey&&typeof t.publicKey=="object"?t.publicKey:t}function D(t){const e=String(t||""),n="=".repeat((4-e.length%4)%4),l=(e+n).replace(/-/g,"+").replace(/_/g,"/"),y=window.atob(l),i=new Uint8Array(y.length);for(let w=0;w<y.length;w+=1)i[w]=y.charCodeAt(w);return i}function h(t){const e=t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t||[]);let n="";for(let l=0;l<e.length;l+=1)n+=String.fromCharCode(e[l]);return window.btoa(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function oe(t){const e=le(t),n={...e,challenge:D(e.challenge),user:{...e.user,id:D(e.user?.id)}};return Array.isArray(e.excludeCredentials)&&(n.excludeCredentials=e.excludeCredentials.map(l=>({...l,id:D(l.id)}))),n}function ie(t){if(!t)return null;const e=t.response||{},n={id:t.id,rawId:h(t.rawId),type:t.type,authenticatorAttachment:t.authenticatorAttachment||void 0,response:{}};return e.clientDataJSON&&(n.response.clientDataJSON=h(e.clientDataJSON)),e.attestationObject&&(n.response.attestationObject=h(e.attestationObject)),e.authenticatorData&&(n.response.authenticatorData=h(e.authenticatorData)),e.signature&&(n.response.signature=h(e.signature)),e.userHandle?n.response.userHandle=h(e.userHandle):n.response.userHandle=null,typeof e.getTransports=="function"&&(n.response.transports=e.getTransports()||[]),n}function ue(){return typeof window<"u"&&window.isSecureContext&&!!window.PublicKeyCredential&&!!navigator.credentials}function de(){const t=String(window?.navigator?.userAgent||"");return/MiuiBrowser|XiaoMi\/MiuiBrowser/i.test(t)}function ce(t,e="Passkey操作"){const n=String(t?.name||"").trim(),l=String(t?.message||"").trim();return n==="NotAllowedError"?`${e}未完成(可能已取消、超时或设备未响应)`:n==="NotReadableError"?/credential manager/i.test(l)&&de()?"当前小米浏览器与系统凭据管理器兼容性较差,请改用系统 Chrome 或 Edge 后重试。":/credential manager/i.test(l)?"系统凭据管理器返回异常,请确认已设置系统锁屏并改用系统 Chrome/Edge 后重试。":l||`${e}失败(设备读取异常)`:n==="SecurityError"?"当前环境安全策略不满足 Passkey 要求,请确认使用 HTTPS 且证书有效。":l||`${e}失败`}async function pe(t){const e=oe(t),n=await navigator.credentials.create({publicKey:e});return ie(n)}const fe={class:"page-stack"},me=24e4,ye={__name:"SettingsPage",setup(t){const e=p(""),n=p(""),l=p(""),y=p(""),i=p(!1),w=p(!1),V=p(!1),A=p(""),k=p([]),g=p(null),_=p(0);function H(s){const a=String(s||"");return a.length<8?{ok:!1,message:"密码长度至少8位"}:a.length>128?{ok:!1,message:"密码长度不能超过128个字符"}:!/[a-zA-Z]/.test(a)||!/\d/.test(a)?{ok:!1,message:"密码必须包含字母和数字"}:{ok:!0,message:""}}async function U(){try{await te()}catch{}finally{window.location.href="/yuyx"}}async function I(){const s=e.value.trim();if(!s){u.error("请输入新用户名");return}try{await O.confirm(`确定将管理员用户名修改为「${s}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}i.value=!0;try{await W(s),u.success("用户名修改成功,请重新登录"),e.value="",setTimeout(U,1200)}catch{}finally{i.value=!1}}async function R(){const s=n.value,a=l.value,d=y.value;if(!s){u.error("请输入当前密码");return}if(!a){u.error("请输入新密码");return}const f=H(a);if(!f.ok){u.error(f.message);return}if(a!==d){u.error("两次输入的新密码不一致");return}try{await O.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}i.value=!0;try{await ee({currentPassword:s,newPassword:a}),u.success("密码修改成功,请重新登录"),n.value="",l.value="",y.value="",setTimeout(U,1200)}catch{}finally{i.value=!1}}async function T(){w.value=!0;try{const s=await ae();k.value=Array.isArray(s?.items)?s.items:[],k.value.length<3?await M():(g.value=null,_.value=0)}catch{k.value=[],g.value=null,_.value=0}finally{w.value=!1}}function j(){return!g.value||Date.now()-Number(_.value||0)>me?null:g.value}async function M(){try{const s=await $({});g.value=s,_.value=Date.now()}catch{g.value=null,_.value=0}}async function z(){if(!ue()){u.error("当前浏览器或环境不支持Passkey需 HTTPS");return}if(k.value.length>=3){u.error("最多可绑定3台设备");return}V.value=!0;try{let s=j();s||(s=await $({}));const a=await pe(s?.publicKey||{});await ne({credential:a,device_name:A.value.trim()}),g.value=null,_.value=0,A.value="",u.success("Passkey设备添加成功"),await T()}catch(s){try{await re({stage:"register",source:"admin-settings",name:s?.name||"",message:s?.message||"",code:s?.code||"",user_agent:navigator.userAgent||""})}catch{}g.value=null,_.value=0,await M();const d=s?.response?.data?.error||ce(s,"Passkey注册");u.error(d)}finally{V.value=!1}}async function J(s){try{await O.confirm(`确定删除设备「${s?.device_name||"未命名设备"}」吗?`,"删除Passkey设备",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"})}catch{return}try{await se(s.id),u.success("设备已删除"),await T()}catch(a){const d=a?.response?.data;u.error(d?.error||"删除失败")}}return Y(()=>{T()}),(s,a)=>{const d=m("el-input"),f=m("el-form-item"),E=m("el-form"),x=m("el-button"),B=m("el-card"),F=m("el-alert"),X=m("el-empty"),P=m("el-table-column"),q=m("el-table"),G=Z("loading");return C(),N("div",fe,[a[13]||(a[13]=b("div",{class:"app-page-title"},[b("h2",null,"设置"),b("span",{class:"app-muted"},"管理员账号设置")],-1)),r(B,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:o(()=>[a[6]||(a[6]=b("h3",{class:"section-title"},"修改管理员用户名",-1)),r(E,{"label-width":"120px"},{default:o(()=>[r(f,{label:"新用户名"},{default:o(()=>[r(d,{modelValue:e.value,"onUpdate:modelValue":a[0]||(a[0]=c=>e.value=c),placeholder:"输入新用户名",disabled:i.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),r(x,{type:"primary",loading:i.value,onClick:I},{default:o(()=>[...a[5]||(a[5]=[S("保存用户名",-1)])]),_:1},8,["loading"])]),_:1}),r(B,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:o(()=>[a[8]||(a[8]=b("h3",{class:"section-title"},"修改管理员密码",-1)),r(E,{"label-width":"120px"},{default:o(()=>[r(f,{label:"当前密码"},{default:o(()=>[r(d,{modelValue:n.value,"onUpdate:modelValue":a[1]||(a[1]=c=>n.value=c),type:"password","show-password":"",placeholder:"输入当前密码",disabled:i.value},null,8,["modelValue","disabled"])]),_:1}),r(f,{label:"新密码"},{default:o(()=>[r(d,{modelValue:l.value,"onUpdate:modelValue":a[2]||(a[2]=c=>l.value=c),type:"password","show-password":"",placeholder:"输入新密码",disabled:i.value},null,8,["modelValue","disabled"])]),_:1}),r(f,{label:"确认新密码"},{default:o(()=>[r(d,{modelValue:y.value,"onUpdate:modelValue":a[3]||(a[3]=c=>y.value=c),type:"password","show-password":"",placeholder:"再次输入新密码",disabled:i.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),r(x,{type:"primary",loading:i.value,onClick:R},{default:o(()=>[...a[7]||(a[7]=[S("保存密码",-1)])]),_:1},8,["loading"]),a[9]||(a[9]=b("div",{class:"help"},"建议使用更强密码至少8位且包含字母与数字。",-1))]),_:1}),r(B,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:o(()=>[a[12]||(a[12]=b("h3",{class:"section-title"},"Passkey设备",-1)),r(F,{type:"info",closable:!1,title:"最多可绑定3台设备可用于管理员无密码登录。","show-icon":"",class:"help-alert"}),r(E,{inline:""},{default:o(()=>[r(f,{label:"设备备注"},{default:o(()=>[r(d,{modelValue:A.value,"onUpdate:modelValue":a[4]||(a[4]=c=>A.value=c),placeholder:"例如值班iPhone / 办公Mac",maxlength:"40","show-word-limit":""},null,8,["modelValue"])]),_:1}),r(f,null,{default:o(()=>[r(x,{type:"primary",loading:V.value,onClick:z},{default:o(()=>[...a[10]||(a[10]=[S("添加Passkey设备",-1)])]),_:1},8,["loading"])]),_:1})]),_:1}),Q((C(),N("div",null,[k.value.length===0?(C(),K(X,{key:0,description:"暂无Passkey设备"})):(C(),K(q,{key:1,data:k.value,size:"small",style:{width:"100%"}},{default:o(()=>[r(P,{prop:"device_name",label:"设备备注","min-width":"160"}),r(P,{prop:"credential_id_preview",label:"凭据ID","min-width":"180"}),r(P,{prop:"last_used_at",label:"最近使用","min-width":"140"}),r(P,{prop:"created_at",label:"创建时间","min-width":"140"}),r(P,{label:"操作",width:"100",fixed:"right"},{default:o(({row:c})=>[r(x,{type:"danger",text:"",onClick:we=>J(c)},{default:o(()=>[...a[11]||(a[11]=[S("删除",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"]))])),[[G,w.value]])]),_:1})])}}},he=L(ye,[["__scopeId","data-v-fb202365"]]);export{he as default};