mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 23:56:42 +08:00
feat #1763
This commit is contained in:
+2
-1
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -850,6 +850,9 @@ export interface User {
|
|||||||
|
|
||||||
// 头像
|
// 头像
|
||||||
avatar: string
|
avatar: string
|
||||||
|
|
||||||
|
// 是否开启二次验证
|
||||||
|
is_otp: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存储空间
|
// 存储空间
|
||||||
|
|||||||
+12
-3
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user