This commit is contained in:
zss
2024-03-28 16:35:20 +08:00
parent eee0c0c878
commit 34b418af96
6 changed files with 154 additions and 7 deletions
+2 -1
View File
@@ -37,6 +37,7 @@
"postcss-purgecss": "^5.0.0", "postcss-purgecss": "^5.0.0",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"pull-refresh-vue3": "^0.3.1", "pull-refresh-vue3": "^0.3.1",
"qrcode.vue": "^3.4.1",
"roboto-fontface": "^0.10.0", "roboto-fontface": "^0.10.0",
"sass": "^1.59.3", "sass": "^1.59.3",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
@@ -109,4 +110,4 @@
"resolutions": { "resolutions": {
"postcss": "8" "postcss": "8"
} }
} }
+3
View File
@@ -850,6 +850,9 @@ export interface User {
// 头像 // 头像
avatar: string avatar: string
// 是否开启二次验证
is_otp: boolean
} }
// 存储空间 // 存储空间
+12 -3
View File
@@ -13,6 +13,7 @@ const store = useStore()
const form = ref({ const form = ref({
username: '', username: '',
password: '', password: '',
otp_password: '',
remember: true, remember: true,
}) })
@@ -55,6 +56,7 @@ function login() {
formData.append('username', form.value.username) formData.append('username', form.value.username)
formData.append('password', form.value.password) formData.append('password', form.value.password)
formData.append('otp_password', form.value.otp_password)
// 请求token // 请求token
api api
@@ -85,13 +87,13 @@ function login() {
if (!error.response) if (!error.response)
errorMessage.value = '登录失败,请检查网络连接' errorMessage.value = '登录失败,请检查网络连接'
else if (error.response.status === 401) else if (error.response.status === 401)
errorMessage.value = '登录失败,请检查用户名密码是否正确' errorMessage.value = '登录失败,请检查用户名密码或二次验证是否正确'
else if (error.response.status === 403) else if (error.response.status === 403)
errorMessage.value = '登录失败,您没有权限访问' errorMessage.value = '登录失败,您没有权限访问'
else if (error.response.status === 500) else if (error.response.status === 500)
errorMessage.value = '登录失败,服务器错误' errorMessage.value = '登录失败,服务器错误'
else else
errorMessage.value = `登录失败 ${error.response.status},请检查用户名密码是否正确` errorMessage.value = `登录失败 ${error.response.status},请检查用户名密码或二次验证是否正确`
}) })
} }
@@ -174,7 +176,14 @@ onMounted(() => {
> >
{{ errorMessage }} {{ errorMessage }}
</div> </div>
</VCol>
<VCol cols="12">
<VTextField
hint="非必传,如开启二次验证需填写"
v-model="form.otp_password"
label="二次验证"
type="input"
/>
<!-- remember me checkbox --> <!-- remember me checkbox -->
<div class="d-flex align-center justify-space-between flex-wrap mt-1 mb-4"> <div class="d-flex align-center justify-space-between flex-wrap mt-1 mb-4">
<VCheckbox <VCheckbox
+130 -1
View File
@@ -4,6 +4,7 @@ import { requiredValidator } from '@/@validators'
import api from '@/api' import api from '@/api'
import type { User } from '@/api/types' import type { User } from '@/api/types'
import avatar1 from '@images/avatars/avatar-1.png' import avatar1 from '@images/avatars/avatar-1.png'
import QrcodeVue from 'qrcode.vue'
const isNewPasswordVisible = ref(false) const isNewPasswordVisible = ref(false)
const isConfirmPasswordVisible = ref(false) const isConfirmPasswordVisible = ref(false)
@@ -19,6 +20,18 @@ const refInputEl = ref<HTMLElement>()
// 新增用户窗口 // 新增用户窗口
const addUserDialog = ref(false) const addUserDialog = ref(false)
// 开启二次验证窗口
const otpDialog = ref(false)
// otp uri
const otpUri = ref('')
// otp secret
const secret = ref('')
// 确认二次验证密码
const otpPassword = ref('')
// 新增用户表单 // 新增用户表单
const userForm = reactive({ const userForm = reactive({
name: '', name: '',
@@ -35,11 +48,15 @@ const accountInfo = ref<User>({
is_active: false, is_active: false,
is_superuser: false, is_superuser: false,
avatar: '', avatar: '',
is_otp: false
}) })
// 所有用户信息 // 所有用户信息
const allUsers = ref<User[]>([]) const allUsers = ref<User[]>([])
// 二维码信息
const qrCode = ref('')
// changeAvatar function // changeAvatar function
function changeAvatar(file: Event) { function changeAvatar(file: Event) {
const fileReader = new FileReader() const fileReader = new FileReader()
@@ -65,7 +82,7 @@ function resetAvatar() {
async function loadAccountInfo() { async function loadAccountInfo() {
try { try {
const user: User = await api.get('user/current') const user: User = await api.get('user/current')
console.log(user)
accountInfo.value = user accountInfo.value = user
if (!accountInfo.value.avatar) if (!accountInfo.value.avatar)
accountInfo.value.avatar = avatar1 accountInfo.value.avatar = avatar1
@@ -167,6 +184,62 @@ async function addUser() {
} }
} }
// 为当前用户获取Otp Uri
async function getOtpUri() {
try {
const result: { [key: string]: any } = await api.post('user/otp/generate')
if (result.success) {
otpUri.value = result.data.uri
secret.value = result.data.secret
qrCode.value = result.data.uri
otpDialog.value = true
} else {
$toast.error(`获取otp uri失败:${result.message}`)
}
} catch (error) {
console.log(error)
}
}
// 关闭当前用户的二次验证
async function disableOtp() {
try {
const result: { [key: string]: any } = await api.post('user/otp/disable')
if (result.success) {
accountInfo.value.is_otp = false;
$toast.success('关闭二次验证成功!')
}
else {
$toast.error(`关闭otp失败:${result.message}`)
}
} catch (error) {
console.log(error)
}
}
// 启用Otp
async function judgeOtpPassword() {
if (!otpPassword) {
$toast.error('请填写6位验证码')
return
}
try {
const result: { [key: string]: any } = await api.post('user/otp/judge', {'uri': otpUri.value, 'otpPassword': otpPassword.value})
if (result.success) {
$toast.success('开启二次验证成功!')
otpDialog.value = false
accountInfo.value.is_otp = true
}
else {
$toast.error(`开启otp失败:${result.message}`)
}
}
catch (error) {
console.log(error)
}
}
// 加载当前用户数据 // 加载当前用户数据
onMounted(() => { onMounted(() => {
loadAccountInfo() loadAccountInfo()
@@ -222,6 +295,16 @@ onMounted(() => {
class="d-sm-none" class="d-sm-none"
/> />
</VBtn> </VBtn>
<VBtn
:color="accountInfo.is_otp? 'error': 'info'"
@click.stop="accountInfo.is_otp? disableOtp(): getOtpUri()"
>
<VIcon
icon="mdi-account-key"
/>
<span class="d-none d-sm-block">{{accountInfo.is_otp? "关闭验证": "二次验证"}}</span>
</VBtn>
</div> </div>
<p class="text-body-1 mb-0"> <p class="text-body-1 mb-0">
@@ -470,4 +553,50 @@ onMounted(() => {
</VCardActions> </VCardActions>
</VCard> </VCard>
</VDialog> </VDialog>
<!-- 二次验证弹窗 -->
<VDialog
v-model="otpDialog"
max-width="40rem"
persistent
z-index="1010"
>
<!-- 开启二次验证弹窗内容 -->
<VCard title="二次验证">
<VCardText>
<VRow>
<VCol>
<QrcodeVue :value="qrCode" :size="200" max-width="25rem"/>
</VCol>
<VCol>
<VRow>
1你可以使用 Microsoft Authenticator (谷歌或其他支持软件如Keeper等) 软件扫描左侧二维码
或者在 APP 中手动输入以下 Key
<h1>{{secret}}</h1>
</VRow>
<VRow>
2 APP 中获取6位验证码并输入
</VRow>
<VRow>
<VTextField
v-model="otpPassword"
type="text"
label="输入验证码以确认开启双重验证"
autocomplete="otpPassword"
/>
</VRow>
</VCol>
</VRow>
</VCardText>
<VCardActions>
<VBtn @click="otpDialog = false">
取消
</VBtn>
<VSpacer />
<VBtn @click="judgeOtpPassword">
确定
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</template> </template>
+2 -2
View File
@@ -184,7 +184,7 @@ async function saveMediaSetting() {
} }
// 调用API查询下载器设置 // 调用API查询下载器设置
async function loadDownladerSetting() { async function loadDownloaderSetting() {
try { try {
const result1: { [key: string]: any } = await api.get('system/setting/DOWNLOADER') const result1: { [key: string]: any } = await api.get('system/setting/DOWNLOADER')
if (result1.success) if (result1.success)
@@ -330,7 +330,7 @@ async function reloadModule() {
// 加载数据 // 加载数据
onMounted(() => { onMounted(() => {
loadDownladerSetting() loadDownloaderSetting()
loadMediaServerSetting() loadMediaServerSetting()
loadMediaSettings() loadMediaSettings()
}) })
+5
View File
@@ -6461,6 +6461,11 @@ purgecss@^5.0.0:
postcss "^8.4.4" postcss "^8.4.4"
postcss-selector-parser "^6.0.7" postcss-selector-parser "^6.0.7"
qrcode.vue@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/qrcode.vue/-/qrcode.vue-3.4.1.tgz#dd8141da9c4ea07ee56b111cd13eadf123af822a"
integrity sha512-wq/zHsifH4FJ1GXQi8/wNxD1KfQkckIpjK1KPTc/qwYU5/Bkd4me0w4xZSg6EXk6xLBkVDE0zxVagewv5EMAVA==
qs@6.11.0: qs@6.11.0:
version "6.11.0" version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"