feat: 添加谷歌二步验证功能,更新用户头像下拉菜单以支持验证操作

This commit is contained in:
2026-01-20 06:29:19 +07:00
parent 9e6dbed419
commit 220b14be30
8 changed files with 214 additions and 57 deletions

View File

@@ -0,0 +1,149 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { authClient, safeClient } from '@/service/api';
import { localStg } from '@/utils/storage';
const props = defineProps<{
password: string;
}>();
const emit = defineEmits<{
success: [];
}>();
const user = localStg.get('userinfo');
const twoFactorEnabled = computed(() => user?.twoFactorEnabled);
const qrCodeUrl = ref('');
const totpSecret = ref('');
const totpCode = ref('');
const loading = ref(false);
const verifying = ref(false);
const disabling = ref(false);
function generateQRCode(url: string) {
return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(url)}`;
}
async function enableTwoFactor() {
if (twoFactorEnabled.value) {
window.$message?.warning('二步验证已开启');
return;
}
loading.value = true;
const { data } = await safeClient(
authClient.twoFactor.enable({
password: props.password,
issuer: 'my-app-name'
})
);
loading.value = false;
if (data) {
const otpauthUrl = data.value?.totpURI || '';
if (otpauthUrl) {
qrCodeUrl.value = generateQRCode(otpauthUrl);
}
}
}
async function verifyTotp() {
if (!totpCode.value) {
window.$message?.warning('请输入验证码');
return;
}
verifying.value = true;
const { data } = await safeClient(
authClient.twoFactor.verifyTotp({
code: totpCode.value,
trustDevice: false
})
);
verifying.value = false;
window.$message?.success('二步验证开启成功');
// 更新本地用户信息
const updatedUser = localStg.get('userinfo');
if (updatedUser) {
updatedUser.twoFactorEnabled = true;
localStg.set('userinfo', updatedUser);
}
emit('success');
// 重置状态
qrCodeUrl.value = '';
totpSecret.value = '';
totpCode.value = '';
}
async function disableTwoFactor() {
if (!twoFactorEnabled.value) {
window.$message?.warning('二步验证未开启');
return;
}
disabling.value = true;
const { data } = await safeClient(
authClient.twoFactor.disable({
password: props.password
})
);
disabling.value = false;
if (data?.value?.status) {
window.$message?.success('二步验证已关闭');
// 更新本地用户信息
const updatedUser = localStg.get('userinfo');
if (updatedUser) {
updatedUser.twoFactorEnabled = false;
localStg.set('userinfo', updatedUser);
}
emit('success');
}
}
</script>
<template>
<div class="google-auth">
<template v-if="!twoFactorEnabled">
<template v-if="!qrCodeUrl">
<NButton type="primary" :loading="loading" @click="enableTwoFactor">开启二步验证</NButton>
</template>
<template v-else>
<div class="qr-code-container">
<div class="mb-4 text-center">
<p class="mb-2 text-sm text-gray-600">请使用 Google Authenticator 扫描二维码</p>
<img :src="qrCodeUrl" alt="QR Code" class="mx-auto" />
</div>
<NSpace vertical>
<NInput v-model:value="totpCode" placeholder="请输入6位验证码" :maxlength="6" clearable />
<NSpace>
<NButton type="primary" :loading="verifying" @click="verifyTotp">验证并开启</NButton>
<NButton @click="qrCodeUrl = ''">取消</NButton>
</NSpace>
</NSpace>
</div>
</template>
</template>
<template v-else>
<NSpace align="center">
<div class="text-success">
<i class="i-carbon-checkmark-filled mr-1" />
二步验证已开启
</div>
<NButton type="error" :loading="disabling" @click="disableTwoFactor">关闭二步验证</NButton>
</NSpace>
</template>
</div>
</template>
<style lang="css" scoped></style>