mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-04 23:18:44 +08:00
Merge pull request #111 from dh336699/hotfix-history-scofield
This commit is contained in:
Vendored
+5
-4
@@ -6,9 +6,6 @@
|
|||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.formatOnSave": false
|
"editor.formatOnSave": false
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
|
||||||
"editor.formatOnSave": false
|
|
||||||
},
|
|
||||||
"[markdown]": {
|
"[markdown]": {
|
||||||
"editor.defaultFormatter": "DavidAnson.vscode-markdownlint"
|
"editor.defaultFormatter": "DavidAnson.vscode-markdownlint"
|
||||||
},
|
},
|
||||||
@@ -25,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
// Vue
|
// Vue
|
||||||
"[vue]": {
|
"[vue]": {
|
||||||
"editor.formatOnSave": false
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
// Extension: Volar
|
// Extension: Volar
|
||||||
"volar.preview.port": 3000,
|
"volar.preview.port": 3000,
|
||||||
@@ -34,6 +31,10 @@
|
|||||||
"source.fixAll.eslint": "explicit",
|
"source.fixAll.eslint": "explicit",
|
||||||
"source.fixAll.stylelint": "explicit"
|
"source.fixAll.stylelint": "explicit"
|
||||||
},
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.formatOnSave": true
|
||||||
|
},
|
||||||
"eslint.alwaysShowStatus": true,
|
"eslint.alwaysShowStatus": true,
|
||||||
"eslint.format.enable": true,
|
"eslint.format.enable": true,
|
||||||
// Extension: Stylelint
|
// Extension: Stylelint
|
||||||
|
|||||||
+21
-52
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { debounce } from 'lodash'
|
||||||
import { VForm } from 'vuetify/components/VForm'
|
import { VForm } from 'vuetify/components/VForm'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import { requiredValidator } from '@/@validators'
|
import { requiredValidator } from '@/@validators'
|
||||||
@@ -48,9 +49,8 @@ async function fetchBackgroundImage() {
|
|||||||
console.log(error)
|
console.log(error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询是否开启双重验证
|
// 查询是否开启双重验证
|
||||||
async function fetchOTP() {
|
const fetchOTP = debounce(async () => {
|
||||||
const userid = usernameInput.value?.value
|
const userid = usernameInput.value?.value
|
||||||
if (!userid) {
|
if (!userid) {
|
||||||
isOTP.value = false
|
isOTP.value = false
|
||||||
@@ -64,22 +64,22 @@ async function fetchOTP() {
|
|||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
})
|
})
|
||||||
}
|
}, 500)
|
||||||
|
|
||||||
// 加载用户监控面板配置
|
// 加载用户监控面板配置
|
||||||
async function loadDashboardConfig() {
|
async function loadDashboardConfig() {
|
||||||
const response = await api.get('/user/config/Dashboard')
|
const response = await api.get('/user/config/Dashboard')
|
||||||
if (response && response.data && response.data.value) {
|
if (response && response.data && response.data.value) {
|
||||||
const data = JSON.stringify(response.data.value)
|
const data = JSON.stringify(response.data.value)
|
||||||
if (data != localStorage.getItem("MP_DASHBOARD")) {
|
if (data != localStorage.getItem('MP_DASHBOARD')) {
|
||||||
localStorage.setItem("MP_DASHBOARD", data)
|
localStorage.setItem('MP_DASHBOARD', data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试加载用户监控面板配置(本地无配置时才加载)
|
// 尝试加载用户监控面板配置(本地无配置时才加载)
|
||||||
async function tryLoadDashboardConfig() {
|
async function tryLoadDashboardConfig() {
|
||||||
if (localStorage.getItem("MP_DASHBOARD")) {
|
if (localStorage.getItem('MP_DASHBOARD')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await loadDashboardConfig()
|
await loadDashboardConfig()
|
||||||
@@ -128,22 +128,17 @@ function login() {
|
|||||||
store.dispatch('auth/updateSuperUser', superuser)
|
store.dispatch('auth/updateSuperUser', superuser)
|
||||||
store.dispatch('auth/updateUserName', username)
|
store.dispatch('auth/updateUserName', username)
|
||||||
store.dispatch('auth/updateAvatar', avatar)
|
store.dispatch('auth/updateAvatar', avatar)
|
||||||
|
|
||||||
// 登录后处理
|
// 登录后处理
|
||||||
afterLogin()
|
afterLogin()
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
// 登录失败,显示错误提示
|
// 登录失败,显示错误提示
|
||||||
if (!error.response)
|
if (!error.response) errorMessage.value = '登录失败,请检查网络连接'
|
||||||
errorMessage.value = '登录失败,请检查网络连接'
|
else if (error.response.status === 401) errorMessage.value = '登录失败,请检查用户名、密码或双重验证是否正确'
|
||||||
else if (error.response.status === 401)
|
else if (error.response.status === 403) errorMessage.value = '登录失败,您没有权限访问'
|
||||||
errorMessage.value = '登录失败,请检查用户名、密码或双重验证是否正确'
|
else if (error.response.status === 500) errorMessage.value = '登录失败,服务器错误'
|
||||||
else if (error.response.status === 403)
|
else errorMessage.value = `登录失败 ${error.response.status},请检查用户名、密码或双重验证码是否正确`
|
||||||
errorMessage.value = '登录失败,您没有权限访问'
|
|
||||||
else if (error.response.status === 500)
|
|
||||||
errorMessage.value = '登录失败,服务器错误'
|
|
||||||
else
|
|
||||||
errorMessage.value = `登录失败 ${error.response.status},请检查用户名、密码或双重验证码是否正确`
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,8 +151,7 @@ onMounted(() => {
|
|||||||
// 如果token存在,且保持登录状态为true,则跳转到首页
|
// 如果token存在,且保持登录状态为true,则跳转到首页
|
||||||
if (token && remember) {
|
if (token && remember) {
|
||||||
router.push('/')
|
router.push('/')
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// 获取背景图片
|
// 获取背景图片
|
||||||
fetchBackgroundImage()
|
fetchBackgroundImage()
|
||||||
}
|
}
|
||||||
@@ -186,16 +180,11 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<VCardTitle class="font-weight-semibold text-2xl text-uppercase">
|
<VCardTitle class="font-weight-semibold text-2xl text-uppercase"> MoviePilot </VCardTitle>
|
||||||
MoviePilot
|
|
||||||
</VCardTitle>
|
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
|
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VForm
|
<VForm ref="refForm" @submit.prevent="() => {}">
|
||||||
ref="refForm"
|
|
||||||
@submit.prevent="() => {}"
|
|
||||||
>
|
|
||||||
<VRow>
|
<VRow>
|
||||||
<!-- username -->
|
<!-- username -->
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
@@ -214,42 +203,22 @@ onMounted(() => {
|
|||||||
v-model="form.password"
|
v-model="form.password"
|
||||||
label="密码"
|
label="密码"
|
||||||
:type="isPasswordVisible ? 'text' : 'password'"
|
:type="isPasswordVisible ? 'text' : 'password'"
|
||||||
:append-inner-icon="
|
:append-inner-icon="isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||||
isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
|
|
||||||
"
|
|
||||||
:rules="[requiredValidator]"
|
:rules="[requiredValidator]"
|
||||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VTextField
|
<VTextField v-if="isOTP" v-model="form.otp_password" label="双重验证码" type="input" />
|
||||||
v-if="isOTP"
|
|
||||||
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">
|
<div class="d-flex align-center justify-space-between flex-wrap">
|
||||||
<VCheckbox
|
<VCheckbox v-model="form.remember" label="保持登录" required />
|
||||||
v-model="form.remember"
|
|
||||||
label="保持登录"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<!-- login button -->
|
<!-- login button -->
|
||||||
<VBtn
|
<VBtn block type="submit" @click="login"> 登录 </VBtn>
|
||||||
block
|
<div v-if="errorMessage" class="text-error mt-2 text-shadow">
|
||||||
type="submit"
|
|
||||||
@click="login"
|
|
||||||
>
|
|
||||||
登录
|
|
||||||
</VBtn>
|
|
||||||
<div
|
|
||||||
v-if="errorMessage"
|
|
||||||
class="text-error mt-2 text-shadow"
|
|
||||||
>
|
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</div>
|
</div>
|
||||||
</VCol>
|
</VCol>
|
||||||
@@ -262,7 +231,7 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use "@core/scss/pages/page-auth.scss";
|
@use '@core/scss/pages/page-auth.scss';
|
||||||
|
|
||||||
.v-card-item__prepend {
|
.v-card-item__prepend {
|
||||||
padding-inline-end: 0 !important;
|
padding-inline-end: 0 !important;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { debounce } from 'lodash'
|
||||||
import { ref, unref } from 'vue'
|
import { ref, unref } from 'vue'
|
||||||
import { useToast } from 'vue-toast-notification'
|
import { useToast } from 'vue-toast-notification'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
@@ -58,11 +59,12 @@ const headers = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const pageRange = [
|
const pageRange = [
|
||||||
{title: '25', value: 25},
|
{ title: '25', value: 25 },
|
||||||
{title: '50', value: 50},
|
{ title: '50', value: 50 },
|
||||||
{title: '100', value: 100},
|
{ title: '100', value: 100 },
|
||||||
{title: '1000', value: 1000},
|
{ title: '1000', value: 1000 },
|
||||||
{title: 'All', value: -1}]
|
{ title: 'All', value: -1 },
|
||||||
|
]
|
||||||
|
|
||||||
// 数据列表
|
// 数据列表
|
||||||
const dataList = ref<TransferHistory[]>([])
|
const dataList = ref<TransferHistory[]>([])
|
||||||
@@ -112,30 +114,32 @@ const TransferDict: { [key: string]: string } = {
|
|||||||
|
|
||||||
// 分页提示
|
// 分页提示
|
||||||
const pageTip = computed(() => {
|
const pageTip = computed(() => {
|
||||||
const begin = unref(itemsPerPage) * (unref(currentPage) - 1) + 1
|
const begin = unref(itemsPerPage) * (unref(currentPage) - 1) + 1
|
||||||
const end = unref(itemsPerPage) * unref(currentPage) === -1 ? 'ALL' : unref(itemsPerPage) * unref(currentPage)
|
const end = unref(itemsPerPage) * unref(currentPage) === -1 ? 'ALL' : unref(itemsPerPage) * unref(currentPage)
|
||||||
return {
|
return {
|
||||||
begin,
|
begin,
|
||||||
end
|
end,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 分页总数
|
// 分页总数
|
||||||
const totalPage = computed(() => {
|
const totalPage = computed(() => {
|
||||||
const total = Math.ceil(unref(totalItems) /unref(itemsPerPage))
|
const total = Math.ceil(unref(totalItems) / unref(itemsPerPage))
|
||||||
return total
|
return total
|
||||||
})
|
})
|
||||||
|
|
||||||
// 切换页签和搜索词
|
// 切换页签和搜索词
|
||||||
watch(
|
watch(
|
||||||
[() => currentPage.value, () => itemsPerPage.value, () => search.value],
|
[() => currentPage.value, () => itemsPerPage.value, () => search.value],
|
||||||
async () => {
|
debounce(async () => {
|
||||||
await fetchData()
|
await fetchData()
|
||||||
})
|
}, 1000),
|
||||||
|
)
|
||||||
|
|
||||||
// 获取订阅列表数据
|
// 获取订阅列表数据
|
||||||
async function fetchData(page = currentPage.value, count = itemsPerPage.value) {
|
async function fetchData(page = currentPage.value, count = itemsPerPage.value) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.get('history/transfer', {
|
const result: { [key: string]: any } = await api.get('history/transfer', {
|
||||||
params: {
|
params: {
|
||||||
@@ -150,8 +154,7 @@ async function fetchData(page = currentPage.value, count = itemsPerPage.value) {
|
|||||||
searchHintList.value = ['失败', '成功', ...new Set(dataList.value.map(item => item.title || ''))].filter(
|
searchHintList.value = ['失败', '成功', ...new Set(dataList.value.map(item => item.title || ''))].filter(
|
||||||
title => title !== '',
|
title => title !== '',
|
||||||
)
|
)
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -159,12 +162,9 @@ async function fetchData(page = currentPage.value, count = itemsPerPage.value) {
|
|||||||
|
|
||||||
// 根据 type 返回不同的图标
|
// 根据 type 返回不同的图标
|
||||||
function getIcon(type: string) {
|
function getIcon(type: string) {
|
||||||
if (type === '电影')
|
if (type === '电影') return 'mdi-movie'
|
||||||
return 'mdi-movie'
|
else if (type === '电视剧') return 'mdi-television-classic'
|
||||||
else if (type === '电视剧')
|
else return 'mdi-help-circle'
|
||||||
return 'mdi-television-classic'
|
|
||||||
else
|
|
||||||
return 'mdi-help-circle'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除历史记录
|
// 删除历史记录
|
||||||
@@ -184,10 +184,8 @@ async function remove(item: TransferHistory, deleteSrc: boolean, deleteDest: boo
|
|||||||
data: item,
|
data: item,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!result.success)
|
if (!result.success) $toast.error(`删除失败: ${result.msg}`)
|
||||||
$toast.error(`删除失败: ${result.msg}`)
|
} catch (error) {
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,8 +194,7 @@ async function remove(item: TransferHistory, deleteSrc: boolean, deleteDest: boo
|
|||||||
async function removeSingle(deleteSrc: boolean, deleteDest: boolean) {
|
async function removeSingle(deleteSrc: boolean, deleteDest: boolean) {
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
deleteConfirmDialog.value = false
|
deleteConfirmDialog.value = false
|
||||||
if (!currentHistory.value)
|
if (!currentHistory.value) return
|
||||||
return
|
|
||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
await remove(currentHistory.value, deleteSrc, deleteDest)
|
await remove(currentHistory.value, deleteSrc, deleteDest)
|
||||||
@@ -211,8 +208,7 @@ async function removeBatch(deleteSrc: boolean, deleteDest: boolean) {
|
|||||||
deleteConfirmDialog.value = false
|
deleteConfirmDialog.value = false
|
||||||
// 总条数
|
// 总条数
|
||||||
const total = selected.value.length
|
const total = selected.value.length
|
||||||
if (total === 0)
|
if (total === 0) return
|
||||||
return
|
|
||||||
|
|
||||||
// 已处理条数
|
// 已处理条数
|
||||||
let handled = 0
|
let handled = 0
|
||||||
@@ -237,16 +233,13 @@ async function removeBatch(deleteSrc: boolean, deleteDest: boolean) {
|
|||||||
|
|
||||||
// 响应删除操作
|
// 响应删除操作
|
||||||
async function deleteConfirmHandler(deleteSrc: boolean, deleteDest: boolean) {
|
async function deleteConfirmHandler(deleteSrc: boolean, deleteDest: boolean) {
|
||||||
if (currentHistory.value)
|
if (currentHistory.value) await removeSingle(deleteSrc, deleteDest)
|
||||||
await removeSingle(deleteSrc, deleteDest)
|
else await removeBatch(deleteSrc, deleteDest)
|
||||||
else
|
|
||||||
await removeBatch(deleteSrc, deleteDest)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量删除历史记录
|
// 批量删除历史记录
|
||||||
async function removeHistoryBatch() {
|
async function removeHistoryBatch() {
|
||||||
if (selected.value.length === 0)
|
if (selected.value.length === 0) return
|
||||||
return
|
|
||||||
|
|
||||||
// 清空当前操作记录
|
// 清空当前操作记录
|
||||||
currentHistory.value = undefined
|
currentHistory.value = undefined
|
||||||
@@ -257,26 +250,20 @@ async function removeHistoryBatch() {
|
|||||||
|
|
||||||
// 计算根路径
|
// 计算根路径
|
||||||
function getRootPath(path: string, type: string, category: string) {
|
function getRootPath(path: string, type: string, category: string) {
|
||||||
if (!path)
|
if (!path) return ''
|
||||||
return ''
|
|
||||||
|
|
||||||
let index = -2
|
let index = -2
|
||||||
if (type !== '电影')
|
if (type !== '电影') index = -3
|
||||||
index = -3
|
|
||||||
|
|
||||||
if (category)
|
if (category) index -= 1
|
||||||
index -= 1
|
|
||||||
|
|
||||||
if (path.includes('/'))
|
if (path.includes('/')) return path.split('/').slice(0, index).join('/')
|
||||||
return path.split('/').slice(0, index).join('/')
|
else return path.split('\\').slice(0, index).join('\\')
|
||||||
else
|
|
||||||
return path.split('\\').slice(0, index).join('\\')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量重新整理
|
// 批量重新整理
|
||||||
async function retransferBatch() {
|
async function retransferBatch() {
|
||||||
if (selected.value.length === 0)
|
if (selected.value.length === 0) return
|
||||||
return
|
|
||||||
|
|
||||||
// 清空当前操作记录
|
// 清空当前操作记录
|
||||||
currentHistory.value = undefined
|
currentHistory.value = undefined
|
||||||
@@ -292,8 +279,7 @@ async function retransferBatch() {
|
|||||||
const category = selected.value[0].category ?? ''
|
const category = selected.value[0].category ?? ''
|
||||||
// 计算根路径
|
// 计算根路径
|
||||||
redoTarget.value = getRootPath(dest, mediaType, category)
|
redoTarget.value = getRootPath(dest, mediaType, category)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
redoTarget.value = ''
|
redoTarget.value = ''
|
||||||
}
|
}
|
||||||
// 打开识别弹窗
|
// 打开识别弹窗
|
||||||
@@ -329,7 +315,6 @@ const dropdownItems = ref([
|
|||||||
|
|
||||||
// 初始加载数据
|
// 初始加载数据
|
||||||
onMounted(fetchData)
|
onMounted(fetchData)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -337,9 +322,7 @@ onMounted(fetchData)
|
|||||||
<VCardItem>
|
<VCardItem>
|
||||||
<VCardTitle>
|
<VCardTitle>
|
||||||
<VRow>
|
<VRow>
|
||||||
<VCol cols="4" md="6">
|
<VCol cols="4" md="6"> 历史记录 </VCol>
|
||||||
历史记录
|
|
||||||
</VCol>
|
|
||||||
<VCol cols="8" md="6" class="flex">
|
<VCol cols="8" md="6" class="flex">
|
||||||
<VCombobox
|
<VCombobox
|
||||||
key="search_navbar"
|
key="search_navbar"
|
||||||
@@ -389,7 +372,7 @@ onMounted(fetchData)
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #item.src="{ item }">
|
<template #item.src="{ item }">
|
||||||
<small>{{ item?.src }} <br>=> {{ item?.dest }}</small>
|
<small>{{ item?.src }} <br />=> {{ item?.dest }}</small>
|
||||||
</template>
|
</template>
|
||||||
<template #item.mode="{ item }">
|
<template #item.mode="{ item }">
|
||||||
<VChip variant="outlined" color="primary" size="small">
|
<VChip variant="outlined" color="primary" size="small">
|
||||||
@@ -397,14 +380,10 @@ onMounted(fetchData)
|
|||||||
</VChip>
|
</VChip>
|
||||||
</template>
|
</template>
|
||||||
<template #item.status="{ item }">
|
<template #item.status="{ item }">
|
||||||
<VChip v-if="item?.status" color="success" size="small">
|
<VChip v-if="item?.status" color="success" size="small"> 成功 </VChip>
|
||||||
成功
|
|
||||||
</VChip>
|
|
||||||
<v-tooltip v-else :text="item?.errmsg">
|
<v-tooltip v-else :text="item?.errmsg">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<VChip v-bind="props" color="error" size="small">
|
<VChip v-bind="props" color="error" size="small"> 失败 </VChip>
|
||||||
失败
|
|
||||||
</VChip>
|
|
||||||
</template>
|
</template>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</template>
|
</template>
|
||||||
@@ -432,22 +411,13 @@ onMounted(fetchData)
|
|||||||
</VMenu>
|
</VMenu>
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</template>
|
</template>
|
||||||
<template #no-data>
|
<template #no-data> 没有数据 </template>
|
||||||
没有数据
|
|
||||||
</template>
|
|
||||||
</VDataTableVirtual>
|
</VDataTableVirtual>
|
||||||
<div class="flex items-center justify-end">
|
<div class="flex items-center justify-end">
|
||||||
<div class="w-auto">
|
<div class="w-auto">
|
||||||
<VSelect
|
<VSelect v-model="itemsPerPage" :items="pageRange" density="compact" variant="solo" flat />
|
||||||
v-model="itemsPerPage"
|
|
||||||
:items="pageRange"
|
|
||||||
density="compact"
|
|
||||||
variant="solo"
|
|
||||||
flat
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="w-auto text-sm">{{pageTip.begin}}-{{pageTip.end}} / {{totalItems}}</div>
|
<div class="w-auto text-sm">{{ pageTip.begin }}-{{ pageTip.end }} / {{ totalItems }}</div>
|
||||||
<VPagination
|
<VPagination
|
||||||
v-model="currentPage"
|
v-model="currentPage"
|
||||||
show-first-last-page
|
show-first-last-page
|
||||||
@@ -466,12 +436,8 @@ onMounted(fetchData)
|
|||||||
{{ confirmTitle }}
|
{{ confirmTitle }}
|
||||||
</VCardTitle>
|
</VCardTitle>
|
||||||
<div class="d-flex flex-column flex-lg-row justify-center my-3">
|
<div class="d-flex flex-column flex-lg-row justify-center my-3">
|
||||||
<VBtn color="primary" class="mb-2 mx-2" @click="deleteConfirmHandler(false, false)">
|
<VBtn color="primary" class="mb-2 mx-2" @click="deleteConfirmHandler(false, false)"> 仅删除历史记录 </VBtn>
|
||||||
仅删除历史记录
|
<VBtn color="warning" class="mb-2 mx-2" @click="deleteConfirmHandler(true, false)"> 删除历史记录和源文件 </VBtn>
|
||||||
</VBtn>
|
|
||||||
<VBtn color="warning" class="mb-2 mx-2" @click="deleteConfirmHandler(true, false)">
|
|
||||||
删除历史记录和源文件
|
|
||||||
</VBtn>
|
|
||||||
<VBtn color="info" class="mb-2 mx-2" @click="deleteConfirmHandler(false, true)">
|
<VBtn color="info" class="mb-2 mx-2" @click="deleteConfirmHandler(false, true)">
|
||||||
删除历史记录和媒体库文件
|
删除历史记录和媒体库文件
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|||||||
@@ -252,362 +252,364 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VRow>
|
<div>
|
||||||
<VCol cols="12">
|
<VRow>
|
||||||
<VCard title="个人信息">
|
<VCol cols="12">
|
||||||
<VCardText class="d-flex">
|
<VCard title="个人信息">
|
||||||
<!-- 👉 Avatar -->
|
<VCardText class="d-flex">
|
||||||
<VAvatar
|
<!-- 👉 Avatar -->
|
||||||
rounded="lg"
|
<VAvatar
|
||||||
size="100"
|
rounded="lg"
|
||||||
class="me-6"
|
size="100"
|
||||||
:image="accountInfo.avatar"
|
class="me-6"
|
||||||
/>
|
:image="accountInfo.avatar"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 👉 Upload Photo -->
|
<!-- 👉 Upload Photo -->
|
||||||
<form class="d-flex flex-column justify-center gap-5">
|
<form class="d-flex flex-column justify-center gap-5">
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="d-flex flex-wrap gap-2">
|
||||||
<VBtn
|
<VBtn
|
||||||
color="primary"
|
color="primary"
|
||||||
@click="refInputEl?.click()"
|
@click="refInputEl?.click()"
|
||||||
>
|
>
|
||||||
<VIcon
|
<VIcon
|
||||||
icon="mdi-cloud-upload-outline"
|
icon="mdi-cloud-upload-outline"
|
||||||
/>
|
/>
|
||||||
<span class="d-none d-sm-block ms-2">上传头像</span>
|
<span class="d-none d-sm-block ms-2">上传头像</span>
|
||||||
</VBtn>
|
|
||||||
|
|
||||||
<input
|
|
||||||
ref="refInputEl"
|
|
||||||
type="file"
|
|
||||||
name="file"
|
|
||||||
accept=".jpeg,.png,.jpg,GIF"
|
|
||||||
hidden
|
|
||||||
@input="changeAvatar"
|
|
||||||
>
|
|
||||||
|
|
||||||
<VBtn
|
|
||||||
type="reset"
|
|
||||||
color="error"
|
|
||||||
variant="tonal"
|
|
||||||
@click="resetAvatar"
|
|
||||||
>
|
|
||||||
<VIcon
|
|
||||||
icon="mdi-refresh"
|
|
||||||
/>
|
|
||||||
<span class="d-none d-sm-block ms-2">重置</span>
|
|
||||||
</VBtn>
|
|
||||||
|
|
||||||
<VBtn
|
|
||||||
:color="accountInfo.is_otp ? 'error' : 'info'"
|
|
||||||
variant="tonal"
|
|
||||||
@click.stop="accountInfo.is_otp ? disableOtp() : getOtpUri()"
|
|
||||||
>
|
|
||||||
<VIcon
|
|
||||||
icon="mdi-account-key"
|
|
||||||
/>
|
|
||||||
<span class="d-none d-sm-block ms-2">{{ accountInfo.is_otp ? "关闭验证" : "双重验证" }}</span>
|
|
||||||
</VBtn>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="text-body-1 mb-0">
|
|
||||||
允许 JPG、GIF 或 PNG 格式, 最大尺寸 800K。
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
</VCardText>
|
|
||||||
|
|
||||||
<VDivider />
|
|
||||||
|
|
||||||
<VCardText>
|
|
||||||
<!-- 👉 Form -->
|
|
||||||
<VForm class="mt-6">
|
|
||||||
<VRow>
|
|
||||||
<!-- 👉 Name -->
|
|
||||||
<VCol
|
|
||||||
md="6"
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="accountInfo.name"
|
|
||||||
readonly
|
|
||||||
label="用户名"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
|
|
||||||
<!-- 👉 Email -->
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="accountInfo.email"
|
|
||||||
label="邮箱"
|
|
||||||
type="email"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<!-- 👉 new password -->
|
|
||||||
<VTextField
|
|
||||||
v-model="newPassword"
|
|
||||||
:type="isNewPasswordVisible ? 'text' : 'password'"
|
|
||||||
:append-inner-icon="isNewPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
|
||||||
label="新密码"
|
|
||||||
autocomplete=""
|
|
||||||
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<!-- 👉 confirm password -->
|
|
||||||
<VTextField
|
|
||||||
v-model="confirmPassword"
|
|
||||||
:type="isConfirmPasswordVisible ? 'text' : 'password'"
|
|
||||||
:append-inner-icon="
|
|
||||||
isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
|
|
||||||
"
|
|
||||||
label="确认新密码"
|
|
||||||
@click:append-inner="
|
|
||||||
isConfirmPasswordVisible = !isConfirmPasswordVisible
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
|
|
||||||
<!-- 👉 Form Actions -->
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
class="d-flex flex-wrap gap-4"
|
|
||||||
>
|
|
||||||
<VBtn @click="saveAccountInfo">
|
|
||||||
保存
|
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|
||||||
|
<input
|
||||||
|
ref="refInputEl"
|
||||||
|
type="file"
|
||||||
|
name="file"
|
||||||
|
accept=".jpeg,.png,.jpg,GIF"
|
||||||
|
hidden
|
||||||
|
@input="changeAvatar"
|
||||||
|
>
|
||||||
|
|
||||||
|
<VBtn
|
||||||
|
type="reset"
|
||||||
|
color="error"
|
||||||
|
variant="tonal"
|
||||||
|
@click="resetAvatar"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="mdi-refresh"
|
||||||
|
/>
|
||||||
|
<span class="d-none d-sm-block ms-2">重置</span>
|
||||||
|
</VBtn>
|
||||||
|
|
||||||
|
<VBtn
|
||||||
|
:color="accountInfo.is_otp ? 'error' : 'info'"
|
||||||
|
variant="tonal"
|
||||||
|
@click.stop="accountInfo.is_otp ? disableOtp() : getOtpUri()"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="mdi-account-key"
|
||||||
|
/>
|
||||||
|
<span class="d-none d-sm-block ms-2">{{ accountInfo.is_otp ? "关闭验证" : "双重验证" }}</span>
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-body-1 mb-0">
|
||||||
|
允许 JPG、GIF 或 PNG 格式, 最大尺寸 800K。
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</VCardText>
|
||||||
|
|
||||||
|
<VDivider />
|
||||||
|
|
||||||
|
<VCardText>
|
||||||
|
<!-- 👉 Form -->
|
||||||
|
<VForm class="mt-6">
|
||||||
|
<VRow>
|
||||||
|
<!-- 👉 Name -->
|
||||||
|
<VCol
|
||||||
|
md="6"
|
||||||
|
cols="12"
|
||||||
|
>
|
||||||
|
<VTextField
|
||||||
|
v-model="accountInfo.name"
|
||||||
|
readonly
|
||||||
|
label="用户名"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Email -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<VTextField
|
||||||
|
v-model="accountInfo.email"
|
||||||
|
label="邮箱"
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<!-- 👉 new password -->
|
||||||
|
<VTextField
|
||||||
|
v-model="newPassword"
|
||||||
|
:type="isNewPasswordVisible ? 'text' : 'password'"
|
||||||
|
:append-inner-icon="isNewPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||||
|
label="新密码"
|
||||||
|
autocomplete=""
|
||||||
|
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<!-- 👉 confirm password -->
|
||||||
|
<VTextField
|
||||||
|
v-model="confirmPassword"
|
||||||
|
:type="isConfirmPasswordVisible ? 'text' : 'password'"
|
||||||
|
:append-inner-icon="
|
||||||
|
isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
|
||||||
|
"
|
||||||
|
label="确认新密码"
|
||||||
|
@click:append-inner="
|
||||||
|
isConfirmPasswordVisible = !isConfirmPasswordVisible
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Form Actions -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
class="d-flex flex-wrap gap-4"
|
||||||
|
>
|
||||||
|
<VBtn @click="saveAccountInfo">
|
||||||
|
保存
|
||||||
|
</VBtn>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</VForm>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<VCol
|
||||||
|
v-if="accountInfo.is_superuser"
|
||||||
|
cols="12"
|
||||||
|
>
|
||||||
|
<!-- 👉 Accounts -->
|
||||||
|
<VCard title="所有用户">
|
||||||
|
<template #append>
|
||||||
|
<IconBtn @click.stop="addUserDialog = true">
|
||||||
|
<VIcon icon="mdi-plus" />
|
||||||
|
</IconBtn>
|
||||||
|
</template>
|
||||||
|
<VTable class="text-no-wrap">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">
|
||||||
|
用户名
|
||||||
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
邮箱
|
||||||
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
状态
|
||||||
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
管理员
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="w-5"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="user in allUsers"
|
||||||
|
:key="user.name"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
{{ user.name }}
|
||||||
|
</td>
|
||||||
|
<td>{{ user.email }}</td>
|
||||||
|
<td>
|
||||||
|
<VChip
|
||||||
|
v-if="user.is_active"
|
||||||
|
color="success"
|
||||||
|
text-color="white"
|
||||||
|
>
|
||||||
|
激活
|
||||||
|
</VChip>
|
||||||
|
<VChip
|
||||||
|
v-else
|
||||||
|
color="error"
|
||||||
|
text-color="white"
|
||||||
|
>
|
||||||
|
冻结
|
||||||
|
</VChip>
|
||||||
|
</td>
|
||||||
|
<td>{{ user.is_superuser ? "是" : "否" }}</td>
|
||||||
|
<td>
|
||||||
|
<IconBtn v-show="accountInfo.is_superuser && accountInfo.name !== user.name">
|
||||||
|
<VIcon icon="mdi-dots-vertical" />
|
||||||
|
<VMenu
|
||||||
|
activator="parent"
|
||||||
|
close-on-content-click
|
||||||
|
>
|
||||||
|
<VList>
|
||||||
|
<VListItem
|
||||||
|
variant="plain"
|
||||||
|
@click="deactivateUser(user)"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<VIcon icon="mdi-lock" />
|
||||||
|
</template>
|
||||||
|
<VListItemTitle>
|
||||||
|
{{
|
||||||
|
user.is_active ? "冻结" : "解冻"
|
||||||
|
}}
|
||||||
|
</VListItemTitle>
|
||||||
|
</VListItem>
|
||||||
|
<VListItem
|
||||||
|
variant="plain"
|
||||||
|
base-color="error"
|
||||||
|
@click="deleteUser(user)"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<VIcon icon="mdi-delete" />
|
||||||
|
</template>
|
||||||
|
<VListItemTitle>删除</VListItemTitle>
|
||||||
|
</VListItem>
|
||||||
|
</VList>
|
||||||
|
</VMenu>
|
||||||
|
</IconBtn>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</VTable>
|
||||||
|
</VCard>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
<!-- =弹窗 -->
|
||||||
|
<VDialog
|
||||||
|
v-model="addUserDialog"
|
||||||
|
max-width="50rem"
|
||||||
|
persistent
|
||||||
|
z-index="1010"
|
||||||
|
>
|
||||||
|
<!-- Dialog Content -->
|
||||||
|
<VCard title="新增用户">
|
||||||
|
<VCardText>
|
||||||
|
<VForm @submit.prevent="() => {}">
|
||||||
|
<VRow>
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<VTextField
|
||||||
|
v-model="userForm.name"
|
||||||
|
label="用户名"
|
||||||
|
:rules="[requiredValidator]"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<VTextField
|
||||||
|
v-model="userForm.password"
|
||||||
|
label="密码"
|
||||||
|
:rules="[requiredValidator]"
|
||||||
|
:type="isPasswordVisible ? 'text' : 'password'"
|
||||||
|
:append-inner-icon="
|
||||||
|
isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
|
||||||
|
"
|
||||||
|
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<VTextField
|
||||||
|
v-model="userForm.email"
|
||||||
|
:rules="[requiredValidator]"
|
||||||
|
label="邮箱"
|
||||||
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
</VForm>
|
</VForm>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
|
<VCardActions>
|
||||||
|
<VBtn @click="addUserDialog = false">
|
||||||
|
取消
|
||||||
|
</VBtn>
|
||||||
|
<VSpacer />
|
||||||
|
<VBtn @click="addUser">
|
||||||
|
确定
|
||||||
|
</VBtn>
|
||||||
|
</VCardActions>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VCol>
|
</VDialog>
|
||||||
|
|
||||||
<VCol
|
<!-- 双重验证弹窗 -->
|
||||||
v-if="accountInfo.is_superuser"
|
<VDialog
|
||||||
cols="12"
|
v-model="otpDialog"
|
||||||
|
max-width="45rem"
|
||||||
|
persistent
|
||||||
|
z-index="1010"
|
||||||
>
|
>
|
||||||
<!-- 👉 Accounts -->
|
<!-- 开启双重验证弹窗内容 -->
|
||||||
<VCard title="所有用户">
|
<VCard>
|
||||||
<template #append>
|
<DialogCloseBtn @click="otpDialog = false" />
|
||||||
<IconBtn @click.stop="addUserDialog = true">
|
<VCardText>
|
||||||
<VIcon icon="mdi-plus" />
|
<h4 class="text-h4 text-center mb-6 mt-5">
|
||||||
</IconBtn>
|
登录双重验证
|
||||||
</template>
|
</h4><h5 class="text-h5 font-weight-medium mb-2">
|
||||||
<VTable class="text-no-wrap">
|
身份验证器
|
||||||
<thead>
|
</h5>
|
||||||
<tr>
|
<p class="mb-6">
|
||||||
<th scope="col">
|
使用像Google Authenticator、Microsoft Authenticator、Authy或1Password这样的身份验证器应用程序,扫描二维码。它将为您生成一个6位数的代码,供您在下方输入。
|
||||||
用户名
|
</p>
|
||||||
</th>
|
<div class="my-6">
|
||||||
<th scope="col">
|
<QrcodeVue class="mx-auto" :value="qrCode" :size="200" max-width="25rem" />
|
||||||
邮箱
|
|
||||||
</th>
|
|
||||||
<th scope="col">
|
|
||||||
状态
|
|
||||||
</th>
|
|
||||||
<th scope="col">
|
|
||||||
管理员
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
class="w-5"
|
|
||||||
/>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="user in allUsers"
|
|
||||||
:key="user.name"
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
{{ user.name }}
|
|
||||||
</td>
|
|
||||||
<td>{{ user.email }}</td>
|
|
||||||
<td>
|
|
||||||
<VChip
|
|
||||||
v-if="user.is_active"
|
|
||||||
color="success"
|
|
||||||
text-color="white"
|
|
||||||
>
|
|
||||||
激活
|
|
||||||
</VChip>
|
|
||||||
<VChip
|
|
||||||
v-else
|
|
||||||
color="error"
|
|
||||||
text-color="white"
|
|
||||||
>
|
|
||||||
冻结
|
|
||||||
</VChip>
|
|
||||||
</td>
|
|
||||||
<td>{{ user.is_superuser ? "是" : "否" }}</td>
|
|
||||||
<td>
|
|
||||||
<IconBtn v-show="accountInfo.is_superuser && accountInfo.name !== user.name">
|
|
||||||
<VIcon icon="mdi-dots-vertical" />
|
|
||||||
<VMenu
|
|
||||||
activator="parent"
|
|
||||||
close-on-content-click
|
|
||||||
>
|
|
||||||
<VList>
|
|
||||||
<VListItem
|
|
||||||
variant="plain"
|
|
||||||
@click="deactivateUser(user)"
|
|
||||||
>
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon icon="mdi-lock" />
|
|
||||||
</template>
|
|
||||||
<VListItemTitle>
|
|
||||||
{{
|
|
||||||
user.is_active ? "冻结" : "解冻"
|
|
||||||
}}
|
|
||||||
</VListItemTitle>
|
|
||||||
</VListItem>
|
|
||||||
<VListItem
|
|
||||||
variant="plain"
|
|
||||||
base-color="error"
|
|
||||||
@click="deleteUser(user)"
|
|
||||||
>
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon icon="mdi-delete" />
|
|
||||||
</template>
|
|
||||||
<VListItemTitle>删除</VListItemTitle>
|
|
||||||
</VListItem>
|
|
||||||
</VList>
|
|
||||||
</VMenu>
|
|
||||||
</IconBtn>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</VTable>
|
|
||||||
</VCard>
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
|
||||||
<!-- =弹窗 -->
|
|
||||||
<VDialog
|
|
||||||
v-model="addUserDialog"
|
|
||||||
max-width="50rem"
|
|
||||||
persistent
|
|
||||||
z-index="1010"
|
|
||||||
>
|
|
||||||
<!-- Dialog Content -->
|
|
||||||
<VCard title="新增用户">
|
|
||||||
<VCardText>
|
|
||||||
<VForm @submit.prevent="() => {}">
|
|
||||||
<VRow>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="userForm.name"
|
|
||||||
label="用户名"
|
|
||||||
:rules="[requiredValidator]"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="userForm.password"
|
|
||||||
label="密码"
|
|
||||||
:rules="[requiredValidator]"
|
|
||||||
:type="isPasswordVisible ? 'text' : 'password'"
|
|
||||||
:append-inner-icon="
|
|
||||||
isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
|
|
||||||
"
|
|
||||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="userForm.email"
|
|
||||||
:rules="[requiredValidator]"
|
|
||||||
label="邮箱"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
|
||||||
</VForm>
|
|
||||||
</VCardText>
|
|
||||||
<VCardActions>
|
|
||||||
<VBtn @click="addUserDialog = false">
|
|
||||||
取消
|
|
||||||
</VBtn>
|
|
||||||
<VSpacer />
|
|
||||||
<VBtn @click="addUser">
|
|
||||||
确定
|
|
||||||
</VBtn>
|
|
||||||
</VCardActions>
|
|
||||||
</VCard>
|
|
||||||
</VDialog>
|
|
||||||
|
|
||||||
<!-- 双重验证弹窗 -->
|
|
||||||
<VDialog
|
|
||||||
v-model="otpDialog"
|
|
||||||
max-width="45rem"
|
|
||||||
persistent
|
|
||||||
z-index="1010"
|
|
||||||
>
|
|
||||||
<!-- 开启双重验证弹窗内容 -->
|
|
||||||
<VCard>
|
|
||||||
<DialogCloseBtn @click="otpDialog = false" />
|
|
||||||
<VCardText>
|
|
||||||
<h4 class="text-h4 text-center mb-6 mt-5">
|
|
||||||
登录双重验证
|
|
||||||
</h4><h5 class="text-h5 font-weight-medium mb-2">
|
|
||||||
身份验证器
|
|
||||||
</h5>
|
|
||||||
<p class="mb-6">
|
|
||||||
使用像Google Authenticator、Microsoft Authenticator、Authy或1Password这样的身份验证器应用程序,扫描二维码。它将为您生成一个6位数的代码,供您在下方输入。
|
|
||||||
</p>
|
|
||||||
<div class="my-6">
|
|
||||||
<QrcodeVue class="mx-auto" :value="qrCode" :size="200" max-width="25rem" />
|
|
||||||
</div>
|
|
||||||
<VAlert
|
|
||||||
:title="secret"
|
|
||||||
variant="tonal"
|
|
||||||
type="warning"
|
|
||||||
class="my-4"
|
|
||||||
text="如果您在使用二维码时遇到困难,请在您的应用程序中选择手动输入以上代码。"
|
|
||||||
>
|
|
||||||
<template #prepend />
|
|
||||||
</VAlert>
|
|
||||||
<VForm>
|
|
||||||
<VTextField
|
|
||||||
v-model="otpPassword"
|
|
||||||
type="text"
|
|
||||||
label="输入验证码以确认开启双重验证"
|
|
||||||
autocomplete=""
|
|
||||||
class="mb-8"
|
|
||||||
variant="outlined"
|
|
||||||
/>
|
|
||||||
<div class="d-flex justify-end flex-wrap gap-4">
|
|
||||||
<VBtn variant="outlined" color="secondary" @click="otpDialog = false">
|
|
||||||
取消
|
|
||||||
</VBtn>
|
|
||||||
<VBtn @click="judgeOtpPassword">
|
|
||||||
确定
|
|
||||||
<template #append>
|
|
||||||
<VIcon icon="mdi-check" />
|
|
||||||
</template>
|
|
||||||
</VBtn>
|
|
||||||
</div>
|
</div>
|
||||||
</VForm>
|
<VAlert
|
||||||
</VCardText>
|
:title="secret"
|
||||||
</VCard>
|
variant="tonal"
|
||||||
</VDialog>
|
type="warning"
|
||||||
|
class="my-4"
|
||||||
|
text="如果您在使用二维码时遇到困难,请在您的应用程序中选择手动输入以上代码。"
|
||||||
|
>
|
||||||
|
<template #prepend />
|
||||||
|
</VAlert>
|
||||||
|
<VForm>
|
||||||
|
<VTextField
|
||||||
|
v-model="otpPassword"
|
||||||
|
type="text"
|
||||||
|
label="输入验证码以确认开启双重验证"
|
||||||
|
autocomplete=""
|
||||||
|
class="mb-8"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
<div class="d-flex justify-end flex-wrap gap-4">
|
||||||
|
<VBtn variant="outlined" color="secondary" @click="otpDialog = false">
|
||||||
|
取消
|
||||||
|
</VBtn>
|
||||||
|
<VBtn @click="judgeOtpPassword">
|
||||||
|
确定
|
||||||
|
<template #append>
|
||||||
|
<VIcon icon="mdi-check" />
|
||||||
|
</template>
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</VForm>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</VDialog>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user