mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-11 18:10:49 +08:00
feat #1763
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
"postcss-purgecss": "^5.0.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"pull-refresh-vue3": "^0.3.1",
|
||||
"qrcode.vue": "^3.4.1",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"sass": "^1.59.3",
|
||||
"tailwindcss": "^3.3.2",
|
||||
@@ -109,4 +110,4 @@
|
||||
"resolutions": {
|
||||
"postcss": "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -850,6 +850,9 @@ export interface User {
|
||||
|
||||
// 头像
|
||||
avatar: string
|
||||
|
||||
// 是否开启二次验证
|
||||
is_otp: boolean
|
||||
}
|
||||
|
||||
// 存储空间
|
||||
|
||||
@@ -13,6 +13,7 @@ const store = useStore()
|
||||
const form = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
otp_password: '',
|
||||
remember: true,
|
||||
})
|
||||
|
||||
@@ -55,6 +56,7 @@ function login() {
|
||||
|
||||
formData.append('username', form.value.username)
|
||||
formData.append('password', form.value.password)
|
||||
formData.append('otp_password', form.value.otp_password)
|
||||
|
||||
// 请求token
|
||||
api
|
||||
@@ -85,13 +87,13 @@ function login() {
|
||||
if (!error.response)
|
||||
errorMessage.value = '登录失败,请检查网络连接'
|
||||
else if (error.response.status === 401)
|
||||
errorMessage.value = '登录失败,请检查用户名和密码是否正确'
|
||||
errorMessage.value = '登录失败,请检查用户名、密码或二次验证是否正确'
|
||||
else if (error.response.status === 403)
|
||||
errorMessage.value = '登录失败,您没有权限访问'
|
||||
else if (error.response.status === 500)
|
||||
errorMessage.value = '登录失败,服务器错误'
|
||||
else
|
||||
errorMessage.value = `登录失败 ${error.response.status},请检查用户名和密码是否正确`
|
||||
errorMessage.value = `登录失败 ${error.response.status},请检查用户名、密码或二次验证是否正确`
|
||||
})
|
||||
}
|
||||
|
||||
@@ -174,7 +176,14 @@ onMounted(() => {
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
hint="非必传,如开启二次验证需填写"
|
||||
v-model="form.otp_password"
|
||||
label="二次验证"
|
||||
type="input"
|
||||
/>
|
||||
<!-- remember me checkbox -->
|
||||
<div class="d-flex align-center justify-space-between flex-wrap mt-1 mb-4">
|
||||
<VCheckbox
|
||||
|
||||
@@ -4,6 +4,7 @@ import { requiredValidator } from '@/@validators'
|
||||
import api from '@/api'
|
||||
import type { User } from '@/api/types'
|
||||
import avatar1 from '@images/avatars/avatar-1.png'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
|
||||
const isNewPasswordVisible = ref(false)
|
||||
const isConfirmPasswordVisible = ref(false)
|
||||
@@ -19,6 +20,18 @@ const refInputEl = ref<HTMLElement>()
|
||||
// 新增用户窗口
|
||||
const addUserDialog = ref(false)
|
||||
|
||||
// 开启二次验证窗口
|
||||
const otpDialog = ref(false)
|
||||
|
||||
// otp uri
|
||||
const otpUri = ref('')
|
||||
|
||||
// otp secret
|
||||
const secret = ref('')
|
||||
|
||||
// 确认二次验证密码
|
||||
const otpPassword = ref('')
|
||||
|
||||
// 新增用户表单
|
||||
const userForm = reactive({
|
||||
name: '',
|
||||
@@ -35,11 +48,15 @@ const accountInfo = ref<User>({
|
||||
is_active: false,
|
||||
is_superuser: false,
|
||||
avatar: '',
|
||||
is_otp: false
|
||||
})
|
||||
|
||||
// 所有用户信息
|
||||
const allUsers = ref<User[]>([])
|
||||
|
||||
// 二维码信息
|
||||
const qrCode = ref('')
|
||||
|
||||
// changeAvatar function
|
||||
function changeAvatar(file: Event) {
|
||||
const fileReader = new FileReader()
|
||||
@@ -65,7 +82,7 @@ function resetAvatar() {
|
||||
async function loadAccountInfo() {
|
||||
try {
|
||||
const user: User = await api.get('user/current')
|
||||
|
||||
console.log(user)
|
||||
accountInfo.value = user
|
||||
if (!accountInfo.value.avatar)
|
||||
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(() => {
|
||||
loadAccountInfo()
|
||||
@@ -222,6 +295,16 @@ onMounted(() => {
|
||||
class="d-sm-none"
|
||||
/>
|
||||
</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>
|
||||
|
||||
<p class="text-body-1 mb-0">
|
||||
@@ -470,4 +553,50 @@ onMounted(() => {
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</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>
|
||||
|
||||
@@ -184,7 +184,7 @@ async function saveMediaSetting() {
|
||||
}
|
||||
|
||||
// 调用API查询下载器设置
|
||||
async function loadDownladerSetting() {
|
||||
async function loadDownloaderSetting() {
|
||||
try {
|
||||
const result1: { [key: string]: any } = await api.get('system/setting/DOWNLOADER')
|
||||
if (result1.success)
|
||||
@@ -330,7 +330,7 @@ async function reloadModule() {
|
||||
|
||||
// 加载数据
|
||||
onMounted(() => {
|
||||
loadDownladerSetting()
|
||||
loadDownloaderSetting()
|
||||
loadMediaServerSetting()
|
||||
loadMediaSettings()
|
||||
})
|
||||
|
||||
@@ -6461,6 +6461,11 @@ purgecss@^5.0.0:
|
||||
postcss "^8.4.4"
|
||||
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:
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
|
||||
|
||||
Reference in New Issue
Block a user