feat(auth): 实现基于Passkey的登录功能

- 添加了对Web Credentials API的支持- 实现了Passkey注册与认证流程
- 引入了TDesign Button组件用于登录按钮
- 更新了页面加载及过渡动画逻辑- 添加了对不支持Passkey设备的降级处理
- 安装并配置了tdesign-icons-vue-next依赖库
This commit is contained in:
Grand-cocoa 2025-11-07 11:50:47 +08:00
parent a2ab4b8bb3
commit 5abf344572
4 changed files with 53 additions and 15 deletions

1
components.d.ts vendored
View File

@ -20,5 +20,6 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
TAvatar: typeof import('tdesign-mobile-vue')['Avatar'] TAvatar: typeof import('tdesign-mobile-vue')['Avatar']
TButton: typeof import('tdesign-mobile-vue')['Button']
} }
} }

1
package-lock.json generated
View File

@ -12,6 +12,7 @@
"pinia": "^3.0.3", "pinia": "^3.0.3",
"sass-loader": "^16.0.6", "sass-loader": "^16.0.6",
"scss": "^0.2.4", "scss": "^0.2.4",
"tdesign-icons-vue-next": "^0.4.1",
"tdesign-mobile-vue": "^1.11.0", "tdesign-mobile-vue": "^1.11.0",
"vue": "^3.5.22", "vue": "^3.5.22",
"vue-router": "^4.6.3" "vue-router": "^4.6.3"

View File

@ -19,6 +19,7 @@
"pinia": "^3.0.3", "pinia": "^3.0.3",
"sass-loader": "^16.0.6", "sass-loader": "^16.0.6",
"scss": "^0.2.4", "scss": "^0.2.4",
"tdesign-icons-vue-next": "^0.4.1",
"tdesign-mobile-vue": "^1.11.0", "tdesign-mobile-vue": "^1.11.0",
"vue": "^3.5.22", "vue": "^3.5.22",
"vue-router": "^4.6.3" "vue-router": "^4.6.3"

View File

@ -5,13 +5,15 @@
<span v-if="showTitle" class="title">你好{{ name }}</span> <span v-if="showTitle" class="title">你好{{ name }}</span>
</transition> </transition>
<div class="login-info"> <div class="login-info">
<transition name="slide-up">
<div v-if="!weCome">
<transition name="slide-up" mode="out-in"> <transition name="slide-up" mode="out-in">
<div v-if="!login"> <div v-if="!login">
<t-button :disabled="!passkey" @click="handleLogin">登录</t-button>
</div>
<div v-else-if="!weCome">
<transition name="slide-up" mode="out-in">
<div v-if="login">
<div class="loading" /> <div class="loading" />
</div> </div>
<div v-else>你好{{ name }}</div>
</transition> </transition>
</div> </div>
</transition> </transition>
@ -20,7 +22,7 @@
<div class="header-placeholder" /> <div class="header-placeholder" />
<div id="operable-box"> <div id="operable-box">
<RouterView v-if="login" ref="routerView" #default="{ Component }"> <RouterView v-if="login" #default="{ Component }">
<transition name="slide-up" mode="out-in"> <transition name="slide-up" mode="out-in">
<component :is="Component" /> <component :is="Component" />
</transition> </transition>
@ -33,16 +35,49 @@ import { RouterView } from 'vue-router'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import avatar from '@/assets/logo.svg' import avatar from '@/assets/logo.svg'
const login = ref(true) const login = ref(false)
const weCome = ref(true) const weCome = ref(false)
const hideLoading = ref(true) const hideLoading = ref(false)
const showTitle = ref(true) const showTitle = ref(false)
const passkey = ref(true)
const name = ref('Amico') const name = ref('Amico')
// mock login
setTimeout(() => { if (!navigator.credentials) {
passkey.value = false
}
function handleLogin(){
navigator.credentials.create({
publicKey: {
challenge: new Uint8Array(32),
timeout: 60000,
rp: {
id: window.location.host,
name: "Animo"
},
user: {
id: new Uint8Array(32),
name: "Amico",
displayName: "Amico",
},
pubKeyCredParams: [{
alg: -7, type: "public-key"
},{
alg: -257, type: "public-key"
}],
excludeCredentials: [],
authenticatorSelection: {
authenticatorAttachment: "platform",
requireResidentKey: true,
}
},
}).then(resp => {
login.value = true login.value = true
}, 1000) })
const routerView = ref() }
//
watch(login, (val) => { watch(login, (val) => {
if (val) { if (val) {
setTimeout(() => { setTimeout(() => {