Update confirmation dialog styles and props in UserProfile.vue, FileList.vue, PluginCard.vue, and dashboard.vue

This commit is contained in:
jxxghp
2024-05-06 17:37:47 +08:00
parent cad8964841
commit c44b20bae3
9 changed files with 179 additions and 531 deletions
-16
View File
@@ -108,14 +108,6 @@ async function uninstallPlugin() {
const isConfirmed = await createConfirm({ const isConfirmed = await createConfirm({
title: '确认', title: '确认',
content: `是否确认卸载插件 ${props.plugin?.plugin_name} ?`, content: `是否确认卸载插件 ${props.plugin?.plugin_name} ?`,
confirmationText: '确认',
cancellationText: '取消',
dialogProps: {
maxWidth: '50rem',
},
confirmationButtonProps: {
variant: 'tonal',
},
}) })
if (!isConfirmed) return if (!isConfirmed) return
@@ -229,14 +221,6 @@ async function resetPlugin() {
const isConfirmed = await createConfirm({ const isConfirmed = await createConfirm({
title: '确认', title: '确认',
content: `是否确认重置插件 ${props.plugin?.plugin_name} 的配置数据?`, content: `是否确认重置插件 ${props.plugin?.plugin_name} 的配置数据?`,
confirmationText: '确认',
cancellationText: '取消',
dialogProps: {
maxWidth: '50rem',
},
confirmationButtonProps: {
variant: 'tonal',
},
}) })
if (!isConfirmed) return if (!isConfirmed) return
+27 -118
View File
@@ -43,16 +43,13 @@ const downloaded = ref<String[]>([])
async function getSiteIcon() { async function getSiteIcon() {
try { try {
siteIcon.value = (await api.get(`site/icon/${torrent?.value?.site}`)).data.icon siteIcon.value = (await api.get(`site/icon/${torrent?.value?.site}`)).data.icon
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
} }
} }
// 询问并添加下载 // 询问并添加下载
async function handleAddDownload(_site: any = undefined, async function handleAddDownload(_site: any = undefined, _media: any = undefined, _torrent: any = undefined) {
_media: any = undefined,
_torrent: any = undefined) {
if (!_media || !_torrent || !_site) { if (!_media || !_torrent || !_site) {
_site = torrent.value?.site_name _site = torrent.value?.site_name
_media = media.value _media = media.value
@@ -62,18 +59,9 @@ async function handleAddDownload(_site: any = undefined,
const isConfirmed = await createConfirm({ const isConfirmed = await createConfirm({
title: '确认', title: '确认',
content: `是否确认下载【${_site}${_torrent?.title} ?`, content: `是否确认下载【${_site}${_torrent?.title} ?`,
confirmationText: '确认',
cancellationText: '取消',
dialogProps: {
maxWidth: '50rem',
},
confirmationButtonProps: {
variant: 'tonal',
},
}) })
if (!isConfirmed) if (!isConfirmed) return
return
addDownload(_media, _torrent) addDownload(_media, _torrent)
} }
@@ -91,13 +79,11 @@ async function addDownload(_media: MediaInfo, _torrent: TorrentInfo) {
// 添加下载成功 // 添加下载成功
$toast.success(`${_torrent?.site_name} ${_torrent?.title} 添加下载成功!`) $toast.success(`${_torrent?.site_name} ${_torrent?.title} 添加下载成功!`)
downloaded.value.push(_torrent?.enclosure || '') downloaded.value.push(_torrent?.enclosure || '')
} } else {
else {
// 添加下载失败 // 添加下载失败
$toast.error(`${_torrent?.site_name} ${_torrent?.title} 添加下载失败!`) $toast.error(`${_torrent?.site_name} ${_torrent?.title} 添加下载失败!`)
} }
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
} }
doneNProgress() doneNProgress()
@@ -115,14 +101,10 @@ async function downloadTorrentFile() {
// 促销Chip类 // 促销Chip类
function getVolumeFactorClass(downloadVolume: number, uploadVolume: number) { function getVolumeFactorClass(downloadVolume: number, uploadVolume: number) {
if (downloadVolume === 0) if (downloadVolume === 0) return 'text-white bg-lime-500'
return 'text-white bg-lime-500' else if (downloadVolume < 1) return 'text-white bg-green-500'
else if (downloadVolume < 1) else if (uploadVolume !== 1) return 'text-white bg-sky-500'
return 'text-white bg-green-500' else return 'text-white bg-gray-500'
else if (uploadVolume !== 1)
return 'text-white bg-sky-500'
else
return 'text-white bg-gray-500'
} }
// 装载时查询站点图标 // 装载时查询站点图标
@@ -138,15 +120,8 @@ onMounted(() => {
:variant="downloaded.includes(torrent?.enclosure || '') ? 'outlined' : 'elevated'" :variant="downloaded.includes(torrent?.enclosure || '') ? 'outlined' : 'elevated'"
@click="handleAddDownload" @click="handleAddDownload"
> >
<template <template v-if="!showMoreTorrents" #image>
v-if="!showMoreTorrents" <VAvatar class="absolute right-2 bottom-2 rounded" variant="flat" rounded="0">
#image
>
<VAvatar
class="absolute right-2 bottom-2 rounded"
variant="flat"
rounded="0"
>
<VImg :src="siteIcon" /> <VImg :src="siteIcon" />
</VAvatar> </VAvatar>
</template> </template>
@@ -159,18 +134,10 @@ onMounted(() => {
<template #append> <template #append>
<div class="me-n3"> <div class="me-n3">
<IconBtn> <IconBtn>
<VIcon <VIcon icon="mdi-dots-vertical" />
icon="mdi-dots-vertical" <VMenu activator="parent" close-on-content-click>
/>
<VMenu
activator="parent"
close-on-content-click
>
<VList> <VList>
<VListItem <VListItem variant="plain" @click="openTorrentDetail()">
variant="plain"
@click="openTorrentDetail()"
>
<template #prepend> <template #prepend>
<VIcon icon="mdi-information" /> <VIcon icon="mdi-information" />
</template> </template>
@@ -196,25 +163,11 @@ onMounted(() => {
{{ torrent?.title }} {{ torrent?.title }}
</VCardText> </VCardText>
<VCardText>{{ torrent?.description }}</VCardText> <VCardText>{{ torrent?.description }}</VCardText>
<VCardItem <VCardItem v-if="torrent?.labels" class="pb-3 pt-0 pe-12">
v-if="torrent?.labels" <VChip v-if="torrent?.hit_and_run" variant="elevated" size="small" class="me-1 mb-1 text-white bg-black">
class="pb-3 pt-0 pe-12"
>
<VChip
v-if="torrent?.hit_and_run"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-black"
>
H&R H&R
</VChip> </VChip>
<VChip <VChip v-if="torrent?.freedate_diff" variant="elevated" color="secondary" size="small" class="me-1 mb-1">
v-if="torrent?.freedate_diff"
variant="elevated"
color="secondary"
size="small"
class="me-1 mb-1"
>
{{ torrent?.freedate_diff }} {{ torrent?.freedate_diff }}
</VChip> </VChip>
<VChip <VChip
@@ -227,51 +180,24 @@ onMounted(() => {
> >
{{ label }} {{ label }}
</VChip> </VChip>
<VChip <VChip v-if="meta?.edition" variant="elevated" size="small" class="me-1 mb-1 text-white bg-red-500">
v-if="meta?.edition"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-red-500"
>
{{ meta?.edition }} {{ meta?.edition }}
</VChip> </VChip>
<VChip <VChip v-if="meta?.resource_pix" variant="elevated" size="small" class="me-1 mb-1 text-white bg-red-500">
v-if="meta?.resource_pix"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-red-500"
>
{{ meta?.resource_pix }} {{ meta?.resource_pix }}
</VChip> </VChip>
<VChip <VChip v-if="meta?.video_encode" variant="elevated" size="small" class="me-1 mb-1 text-white bg-orange-500">
v-if="meta?.video_encode"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-orange-500"
>
{{ meta?.video_encode }} {{ meta?.video_encode }}
</VChip> </VChip>
<VChip <VChip v-if="torrent?.size" variant="elevated" size="small" class="me-1 mb-1 text-white bg-yellow-500">
v-if="torrent?.size"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-yellow-500"
>
{{ formatFileSize(torrent?.size) }} {{ formatFileSize(torrent?.size) }}
</VChip> </VChip>
<VChip <VChip v-if="meta?.resource_team" variant="elevated" size="small" class="me-1 mb-1 text-white bg-cyan-500">
v-if="meta?.resource_team"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-cyan-500"
>
{{ meta?.resource_team }} {{ meta?.resource_team }}
</VChip> </VChip>
<VChip <VChip
v-if="torrent?.downloadvolumefactor !== 1 || torrent?.uploadvolumefactor !== 1" v-if="torrent?.downloadvolumefactor !== 1 || torrent?.uploadvolumefactor !== 1"
:class=" :class="getVolumeFactorClass(torrent?.downloadvolumefactor, torrent?.uploadvolumefactor)"
getVolumeFactorClass(torrent?.downloadvolumefactor, torrent?.uploadvolumefactor)
"
variant="elevated" variant="elevated"
size="small" size="small"
class="me-1 mb-1" class="me-1 mb-1"
@@ -280,10 +206,7 @@ onMounted(() => {
</VChip> </VChip>
</VCardItem> </VCardItem>
<VCardActions> <VCardActions>
<VBtn <VBtn v-if="props.more && props.more.length > 0" @click.stop="showMoreTorrents = !showMoreTorrents">
v-if="props.more && props.more.length > 0"
@click.stop="showMoreTorrents = !showMoreTorrents"
>
<template #append> <template #append>
<VIcon :icon="showMoreTorrents ? 'mdi-chevron-up' : 'mdi-chevron-down'" /> <VIcon :icon="showMoreTorrents ? 'mdi-chevron-up' : 'mdi-chevron-down'" />
</template> </template>
@@ -297,26 +220,12 @@ onMounted(() => {
<VChip <VChip
v-for="(item, index) in props.more" v-for="(item, index) in props.more"
:key="index" :key="index"
@click.stop=" @click.stop="handleAddDownload(item.torrent_info?.site_name, item.media_info, item.torrent_info)"
handleAddDownload(
item.torrent_info?.site_name,
item.media_info,
item.torrent_info,
)
"
> >
<template #append> <template #append>
<VBadge color="primary" :content="`↑${item.torrent_info?.seeders}`" inline size="small" />
<VBadge <VBadge
color="primary" v-if="item.torrent_info?.downloadvolumefactor !== 1 || item.torrent_info?.uploadvolumefactor !== 1"
:content="`↑${item.torrent_info?.seeders}`"
inline
size="small"
/>
<VBadge
v-if="
item.torrent_info?.downloadvolumefactor !== 1
|| item.torrent_info?.uploadvolumefactor !== 1
"
:content="item.torrent_info?.volume_factor" :content="item.torrent_info?.volume_factor"
inline inline
size="small" size="small"
+25 -102
View File
@@ -5,7 +5,7 @@ import { useConfirm } from 'vuetify-use-dialog'
import { formatFileSize } from '@/@core/utils/formatters' import { formatFileSize } from '@/@core/utils/formatters'
import api from '@/api' import api from '@/api'
import { doneNProgress, startNProgress } from '@/api/nprogress' import { doneNProgress, startNProgress } from '@/api/nprogress'
import type { Context, MediaInfo, TorrentInfo } from '@/api/types' import type { Context, MediaInfo, TorrentInfo } from '@/api/types'
// 输入参数 // 输入参数
const props = defineProps({ const props = defineProps({
@@ -40,16 +40,13 @@ const downloaded = ref<String[]>([])
async function getSiteIcon() { async function getSiteIcon() {
try { try {
siteIcon.value = (await api.get(`site/icon/${torrent?.value?.site}`)).data.icon siteIcon.value = (await api.get(`site/icon/${torrent?.value?.site}`)).data.icon
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
} }
} }
// 询问并添加下载 // 询问并添加下载
async function handleAddDownload(_site: any = undefined, async function handleAddDownload(_site: any = undefined, _media: any = undefined, _torrent: any = undefined) {
_media: any = undefined,
_torrent: any = undefined) {
if (!_media || !_torrent || !_site) { if (!_media || !_torrent || !_site) {
_site = torrent.value?.site_name _site = torrent.value?.site_name
_media = media.value _media = media.value
@@ -59,18 +56,9 @@ async function handleAddDownload(_site: any = undefined,
const isConfirmed = await createConfirm({ const isConfirmed = await createConfirm({
title: '确认', title: '确认',
content: `是否确认下载【${_site}${_torrent?.title} ?`, content: `是否确认下载【${_site}${_torrent?.title} ?`,
confirmationText: '确认',
cancellationText: '取消',
dialogProps: {
maxWidth: '50rem',
},
confirmationButtonProps: {
variant: 'tonal',
},
}) })
if (!isConfirmed) if (!isConfirmed) return
return
addDownload(_media, _torrent) addDownload(_media, _torrent)
} }
@@ -88,13 +76,11 @@ async function addDownload(_media: MediaInfo, _torrent: TorrentInfo) {
// 添加下载成功 // 添加下载成功
$toast.success(`${_torrent?.site_name} ${_torrent?.title} 添加下载成功!`) $toast.success(`${_torrent?.site_name} ${_torrent?.title} 添加下载成功!`)
downloaded.value.push(_torrent?.enclosure || '') downloaded.value.push(_torrent?.enclosure || '')
} } else {
else {
// 添加下载失败 // 添加下载失败
$toast.error(`${_torrent?.site_name} ${_torrent?.title} 添加下载失败!`) $toast.error(`${_torrent?.site_name} ${_torrent?.title} 添加下载失败!`)
} }
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
} }
doneNProgress() doneNProgress()
@@ -112,14 +98,10 @@ async function downloadTorrentFile() {
// 促销Chip类 // 促销Chip类
function getVolumeFactorClass(downloadVolume: number, uploadVolume: number) { function getVolumeFactorClass(downloadVolume: number, uploadVolume: number) {
if (downloadVolume === 0) if (downloadVolume === 0) return 'text-white bg-lime-500'
return 'text-white bg-lime-500' else if (downloadVolume < 1) return 'text-white bg-green-500'
else if (downloadVolume < 1) else if (uploadVolume !== 1) return 'text-white bg-sky-500'
return 'text-white bg-green-500' else return 'text-white bg-gray-500'
else if (uploadVolume !== 1)
return 'text-white bg-sky-500'
else
return 'text-white bg-gray-500'
} }
// 装载时查询站点图标 // 装载时查询站点图标
@@ -129,19 +111,9 @@ onMounted(() => {
</script> </script>
<template> <template>
<VListItem <VListItem @click="handleAddDownload" :variant="downloaded.includes(torrent?.enclosure || '') ? 'outlined' : 'flat'">
@click="handleAddDownload" <template v-if="!showMoreTorrents" #prepend>
:variant="downloaded.includes(torrent?.enclosure || '') ? 'outlined' : 'flat'" <VAvatar class="rounded" variant="flat" @click.stop="openTorrentDetail">
>
<template
v-if="!showMoreTorrents"
#prepend
>
<VAvatar
class="rounded"
variant="flat"
@click.stop="openTorrentDetail"
>
<VImg :src="siteIcon" /> <VImg :src="siteIcon" />
</VAvatar> </VAvatar>
</template> </template>
@@ -153,25 +125,11 @@ onMounted(() => {
<VListItemSubtitle> <VListItemSubtitle>
{{ torrent?.description }} {{ torrent?.description }}
</VListItemSubtitle> </VListItemSubtitle>
<div <div v-if="torrent?.labels" class="pt-2">
v-if="torrent?.labels" <VChip v-if="torrent?.hit_and_run" variant="elevated" size="small" class="me-1 mb-1 text-white bg-black">
class="pt-2"
>
<VChip
v-if="torrent?.hit_and_run"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-black"
>
H&R H&R
</VChip> </VChip>
<VChip <VChip v-if="torrent?.freedate_diff" variant="elevated" color="secondary" size="small" class="me-1 mb-1">
v-if="torrent?.freedate_diff"
variant="elevated"
color="secondary"
size="small"
class="me-1 mb-1"
>
{{ torrent?.freedate_diff }} {{ torrent?.freedate_diff }}
</VChip> </VChip>
<VChip <VChip
@@ -184,51 +142,24 @@ onMounted(() => {
> >
{{ label }} {{ label }}
</VChip> </VChip>
<VChip <VChip v-if="meta?.edition" variant="elevated" size="small" class="me-1 mb-1 text-white bg-red-500">
v-if="meta?.edition"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-red-500"
>
{{ meta?.edition }} {{ meta?.edition }}
</VChip> </VChip>
<VChip <VChip v-if="meta?.resource_pix" variant="elevated" size="small" class="me-1 mb-1 text-white bg-red-500">
v-if="meta?.resource_pix"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-red-500"
>
{{ meta?.resource_pix }} {{ meta?.resource_pix }}
</VChip> </VChip>
<VChip <VChip v-if="meta?.video_encode" variant="elevated" size="small" class="me-1 mb-1 text-white bg-orange-500">
v-if="meta?.video_encode"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-orange-500"
>
{{ meta?.video_encode }} {{ meta?.video_encode }}
</VChip> </VChip>
<VChip <VChip v-if="torrent?.size" variant="elevated" size="small" class="me-1 mb-1 text-white bg-yellow-500">
v-if="torrent?.size"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-yellow-500"
>
{{ formatFileSize(torrent?.size) }} {{ formatFileSize(torrent?.size) }}
</VChip> </VChip>
<VChip <VChip v-if="meta?.resource_team" variant="elevated" size="small" class="me-1 mb-1 text-white bg-cyan-500">
v-if="meta?.resource_team"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-cyan-500"
>
{{ meta?.resource_team }} {{ meta?.resource_team }}
</VChip> </VChip>
<VChip <VChip
v-if="torrent?.downloadvolumefactor !== 1 || torrent?.uploadvolumefactor !== 1" v-if="torrent?.downloadvolumefactor !== 1 || torrent?.uploadvolumefactor !== 1"
:class=" :class="getVolumeFactorClass(torrent?.downloadvolumefactor, torrent?.uploadvolumefactor)"
getVolumeFactorClass(torrent?.downloadvolumefactor, torrent?.uploadvolumefactor)
"
variant="elevated" variant="elevated"
size="small" size="small"
class="me-1 mb-1" class="me-1 mb-1"
@@ -239,18 +170,10 @@ onMounted(() => {
<template #append> <template #append>
<div class="me-n3"> <div class="me-n3">
<IconBtn> <IconBtn>
<VIcon <VIcon icon="mdi-dots-vertical" />
icon="mdi-dots-vertical" <VMenu activator="parent" close-on-content-click>
/>
<VMenu
activator="parent"
close-on-content-click
>
<VList> <VList>
<VListItem <VListItem variant="plain" @click="openTorrentDetail()">
variant="plain"
@click="openTorrentDetail()"
>
<template #prepend> <template #prepend>
<VIcon icon="mdi-information" /> <VIcon icon="mdi-information" />
</template> </template>
-8
View File
@@ -117,14 +117,6 @@ async function deleteItem(item: FileItem) {
const confirmed = await createConfirm({ const confirmed = await createConfirm({
title: '确认', title: '确认',
content: `是否确认删除${item.type === 'dir' ? '目录' : '文件'} ${item.basename}`, content: `是否确认删除${item.type === 'dir' ? '目录' : '文件'} ${item.basename}`,
confirmationText: '确认',
cancellationText: '取消',
dialogProps: {
maxWidth: '50rem',
},
cancellationButtonProps: {
variant: 'tonal',
},
}) })
if (confirmed) { if (confirmed) {
+15 -54
View File
@@ -60,22 +60,17 @@ async function getResourceList() {
try { try {
resourceDataList.value = await api.get(`site/resource/${props.site}`) resourceDataList.value = await api.get(`site/resource/${props.site}`)
resourceLoading.value = false resourceLoading.value = false
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
} }
} }
// 促销Chip类 // 促销Chip类
function getVolumeFactorClass(downloadVolume: number, uploadVolume: number) { function getVolumeFactorClass(downloadVolume: number, uploadVolume: number) {
if (downloadVolume === 0) if (downloadVolume === 0) return 'text-white bg-lime-500'
return 'text-white bg-lime-500' else if (downloadVolume < 1) return 'text-white bg-green-500'
else if (downloadVolume < 1) else if (uploadVolume !== 1) return 'text-white bg-sky-500'
return 'text-white bg-green-500' else return 'text-white bg-gray-500'
else if (uploadVolume !== 1)
return 'text-white bg-sky-500'
else
return 'text-white bg-gray-500'
} }
// 添加下载 // 添加下载
@@ -83,18 +78,9 @@ async function addDownload(_torrent: any) {
const isConfirmed = await createConfirm({ const isConfirmed = await createConfirm({
title: '确认', title: '确认',
content: `是否确认下载【${_torrent.site_name}${_torrent?.title} ?`, content: `是否确认下载【${_torrent.site_name}${_torrent?.title} ?`,
confirmationText: '确认',
cancellationText: '取消',
dialogProps: {
maxWidth: '50rem',
},
confirmationButtonProps: {
variant: 'tonal',
},
}) })
if (!isConfirmed) if (!isConfirmed) return
return
startNProgress() startNProgress()
try { try {
@@ -103,13 +89,11 @@ async function addDownload(_torrent: any) {
if (result.success) { if (result.success) {
// 添加下载成功 // 添加下载成功
$toast.success(`${_torrent?.site_name} ${_torrent?.title} 添加下载成功!`) $toast.success(`${_torrent?.site_name} ${_torrent?.title} 添加下载成功!`)
} } else {
else {
// 添加下载失败 // 添加下载失败
$toast.error(`${_torrent?.site_name} ${_torrent?.title} 添加下载失败:${result.message || '未知错误'}`) $toast.error(`${_torrent?.site_name} ${_torrent?.title} 添加下载失败:${result.message || '未知错误'}`)
} }
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
} }
doneNProgress() doneNProgress()
@@ -146,21 +130,10 @@ onMounted(() => {
<div class="text-sm my-1"> <div class="text-sm my-1">
{{ item.description }} {{ item.description }}
</div> </div>
<VChip <VChip v-if="item.hit_and_run" variant="elevated" size="small" class="me-1 mb-1 text-white bg-black">
v-if="item.hit_and_run"
variant="elevated"
size="small"
class="me-1 mb-1 text-white bg-black"
>
H&R H&R
</VChip> </VChip>
<VChip <VChip v-if="item.freedate_diff" variant="elevated" color="secondary" size="small" class="me-1 mb-1">
v-if="item.freedate_diff"
variant="elevated"
color="secondary"
size="small"
class="me-1 mb-1"
>
{{ item.freedate_diff }} {{ item.freedate_diff }}
</VChip> </VChip>
<VChip <VChip
@@ -175,9 +148,7 @@ onMounted(() => {
</VChip> </VChip>
<VChip <VChip
v-if="item.downloadvolumefactor !== 1 || item.uploadvolumefactor !== 1" v-if="item.downloadvolumefactor !== 1 || item.uploadvolumefactor !== 1"
:class=" :class="getVolumeFactorClass(item.downloadvolumefactor, item.uploadvolumefactor)"
getVolumeFactorClass(item.downloadvolumefactor, item.uploadvolumefactor)
"
variant="elevated" variant="elevated"
size="small" size="small"
class="me-1 mb-1" class="me-1 mb-1"
@@ -206,18 +177,10 @@ onMounted(() => {
<template #item.actions="{ item }"> <template #item.actions="{ item }">
<div class="me-n3"> <div class="me-n3">
<IconBtn> <IconBtn>
<VIcon <VIcon icon="mdi-dots-vertical" />
icon="mdi-dots-vertical" <VMenu activator="parent" close-on-content-click>
/>
<VMenu
activator="parent"
close-on-content-click
>
<VList> <VList>
<VListItem <VListItem variant="plain" @click="openTorrentDetail(item.page_url || '')">
variant="plain"
@click="openTorrentDetail(item.page_url || '')"
>
<template #prepend> <template #prepend>
<VIcon icon="mdi-information" /> <VIcon icon="mdi-information" />
</template> </template>
@@ -238,8 +201,6 @@ onMounted(() => {
</IconBtn> </IconBtn>
</div> </div>
</template> </template>
<template #no-data> <template #no-data> 没有数据 </template>
没有数据
</template>
</VDataTable> </VDataTable>
</template> </template>
-8
View File
@@ -34,14 +34,6 @@ async function restart() {
const confirmed = await createConfirm({ const confirmed = await createConfirm({
title: '确认', title: '确认',
content: '确认重启系统吗?', content: '确认重启系统吗?',
confirmationText: '确认',
cancellationText: '取消',
dialogProps: {
maxWidth: '30rem',
},
cancellationButtonProps: {
variant: 'tonal',
},
}) })
if (confirmed) { if (confirmed) {
+24 -4
View File
@@ -15,8 +15,8 @@ import '@core/scss/template/index.scss'
import '@layouts/styles/index.scss' import '@layouts/styles/index.scss'
import '@styles/styles.scss' import '@styles/styles.scss'
import 'vue-toast-notification/dist/theme-bootstrap.css' import 'vue-toast-notification/dist/theme-bootstrap.css'
import { PerfectScrollbarPlugin } from 'vue3-perfect-scrollbar'; import { PerfectScrollbarPlugin } from 'vue3-perfect-scrollbar'
import 'vue3-perfect-scrollbar/style.css'; import 'vue3-perfect-scrollbar/style.css'
import DialogCloseBtn from '@/@core/components/DialogCloseBtn.vue' import DialogCloseBtn from '@/@core/components/DialogCloseBtn.vue'
import { fixArrayAt } from '@/@core/utils/compatibility' import { fixArrayAt } from '@/@core/utils/compatibility'
@@ -30,7 +30,8 @@ loadFonts()
const app = createApp(App) const app = createApp(App)
// 注册全局组件 // 注册全局组件
app.component('VAceEditor', VAceEditor) app
.component('VAceEditor', VAceEditor)
.component('VApexChart', VueApexCharts) .component('VApexChart', VueApexCharts)
.component('VDialogCloseBtn', DialogCloseBtn) .component('VDialogCloseBtn', DialogCloseBtn)
@@ -42,7 +43,26 @@ app
.use(ToastPlugin, { .use(ToastPlugin, {
position: 'bottom-right', position: 'bottom-right',
}) })
.use(VuetifyUseDialog) .use(VuetifyUseDialog, {
confirmDialog: {
dialogProps: {
maxWidth: '50rem',
},
confirmationButtonProps: {
variant: 'elevated',
color: 'primary',
class: 'me-3 px-5',
'prepend-icon': 'mdi-check',
},
cancellationButtonProps: {
variant: 'outlined',
color: 'secondary',
class: 'me-3',
},
confirmationText: '确认',
cancellationText: '取消',
},
})
.use(PerfectScrollbarPlugin) .use(PerfectScrollbarPlugin)
.mount('#app') .mount('#app')
.$nextTick(() => removeEl('#loading-bg')) .$nextTick(() => removeEl('#loading-bg'))
+16 -6
View File
@@ -112,8 +112,12 @@ function setDashboardConfig() {
</VCol> </VCol>
</VRow> </VRow>
<!-- 弹窗根据配置生成选项 --> <!-- 弹窗根据配置生成选项 -->
<VDialog v-model="dialog" max-width="40rem" scrollable :fullscreen="!display.mdAndUp.value"> <VDialog v-model="dialog" max-width="35rem" scrollable :fullscreen="!display.mdAndUp.value">
<VCard title="设置仪表板"> <VCard>
<VCardItem>
<VCardTitle>设置仪表板</VCardTitle>
</VCardItem>
<VDivider />
<VCardText> <VCardText>
<VRow> <VRow>
<VCol v-for="(item, key) in dashboard_names" :key="key" cols="12" md="4" sm="4"> <VCol v-for="(item, key) in dashboard_names" :key="key" cols="12" md="4" sm="4">
@@ -121,11 +125,17 @@ function setDashboardConfig() {
</VCol> </VCol>
</VRow> </VRow>
</VCardText> </VCardText>
<VCardActions> <VDivider />
<VBtn color="primary" @click="dialog = false"> 取消 </VBtn> <VCardText class="pt-5 text-end">
<VSpacer /> <VSpacer />
<VBtn color="primary" variant="tonal" @click="setDashboardConfig"> 保存 </VBtn> <VBtn variant="outlined" color="secondary" class="me-4" @click="dialog = false"> 关闭 </VBtn>
</VCardActions> <VBtn @click="setDashboardConfig">
<template #prepend>
<VIcon icon="mdi-content-save" />
</template>
保存
</VBtn>
</VCardText>
</VCard> </VCard>
</VDialog> </VDialog>
</template> </template>
+72 -215
View File
@@ -85,10 +85,8 @@ async function loadAccountInfo() {
const user: User = await api.get('user/current') const user: User = await api.get('user/current')
console.log(user) 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 } catch (error) {
}
catch (error) {
console.log(error) console.log(error)
} }
} }
@@ -105,12 +103,9 @@ async function saveAccountInfo() {
} }
try { try {
const result: { [key: string]: any } = await api.put('user/', accountInfo.value) const result: { [key: string]: any } = await api.put('user/', accountInfo.value)
if (result.success) if (result.success) $toast.success('用户信息保存成功!')
$toast.success('用户信息保存成功!') else $toast.error(`用户信息保存失败:${result.message}`)
else } catch (error) {
$toast.error(`用户信息保存失败:${result.message}`)
}
catch (error) {
console.log(error) console.log(error)
} }
} }
@@ -121,8 +116,7 @@ async function loadAllUsers() {
const result: User[] = await api.get('/user/') const result: User[] = await api.get('/user/')
allUsers.value = result allUsers.value = result
} } catch (error) {
catch (error) {
console.log(error) console.log(error)
} }
} }
@@ -134,12 +128,10 @@ async function deleteUser(user: User) {
if (result.success) { if (result.success) {
$toast.success('用户删除成功!') $toast.success('用户删除成功!')
loadAllUsers() loadAllUsers()
} } else {
else {
$toast.error(`用户删除失败:${result.message}`) $toast.error(`用户删除失败:${result.message}`)
} }
} } catch (error) {
catch (error) {
console.log(error) console.log(error)
} }
} }
@@ -153,12 +145,10 @@ async function deactivateUser(user: User) {
if (result.success) { if (result.success) {
$toast.success('用户冻结成功!') $toast.success('用户冻结成功!')
loadAllUsers() loadAllUsers()
} } else {
else {
$toast.error(`用户冻结失败:${result.message}`) $toast.error(`用户冻结失败:${result.message}`)
} }
} } catch (error) {
catch (error) {
console.log(error) console.log(error)
} }
} }
@@ -175,12 +165,10 @@ async function addUser() {
$toast.success('用户新增成功!') $toast.success('用户新增成功!')
loadAllUsers() loadAllUsers()
addUserDialog.value = false addUserDialog.value = false
} } else {
else {
$toast.error(`用户新增失败:${result.message}`) $toast.error(`用户新增失败:${result.message}`)
} }
} } catch (error) {
catch (error) {
console.log(error) console.log(error)
} }
} }
@@ -194,12 +182,10 @@ async function getOtpUri() {
secret.value = result.data.secret secret.value = result.data.secret
qrCode.value = result.data.uri qrCode.value = result.data.uri
otpDialog.value = true otpDialog.value = true
} } else {
else {
$toast.error(`获取otp uri失败:${result.message}`) $toast.error(`获取otp uri失败:${result.message}`)
} }
} } catch (error) {
catch (error) {
console.log(error) console.log(error)
} }
} }
@@ -211,12 +197,10 @@ async function disableOtp() {
if (result.success) { if (result.success) {
accountInfo.value.is_otp = false accountInfo.value.is_otp = false
$toast.success('关闭登录双重验证成功!') $toast.success('关闭登录双重验证成功!')
} } else {
else {
$toast.error(`关闭otp失败:${result.message}`) $toast.error(`关闭otp失败:${result.message}`)
} }
} } catch (error) {
catch (error) {
console.log(error) console.log(error)
} }
} }
@@ -228,18 +212,19 @@ async function judgeOtpPassword() {
return return
} }
try { try {
const result: { [key: string]: any } = await api.post('user/otp/judge', { uri: otpUri.value, otpPassword: otpPassword.value }) const result: { [key: string]: any } = await api.post('user/otp/judge', {
uri: otpUri.value,
otpPassword: otpPassword.value,
})
if (result.success) { if (result.success) {
$toast.success('开启登录双重验证成功!') $toast.success('开启登录双重验证成功!')
otpDialog.value = false otpDialog.value = false
accountInfo.value.is_otp = true accountInfo.value.is_otp = true
} } else {
else {
$toast.error(`开启otp失败:${result.message}`) $toast.error(`开启otp失败:${result.message}`)
} }
} } catch (error) {
catch (error) {
console.log(error) console.log(error)
} }
} }
@@ -258,23 +243,13 @@ onMounted(() => {
<VCard title="个人信息"> <VCard title="个人信息">
<VCardText class="d-flex"> <VCardText class="d-flex">
<!-- 👉 Avatar --> <!-- 👉 Avatar -->
<VAvatar <VAvatar rounded="lg" size="100" class="me-6" :image="accountInfo.avatar" />
rounded="lg"
size="100"
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" @click="refInputEl?.click()">
color="primary" <VIcon icon="mdi-cloud-upload-outline" />
@click="refInputEl?.click()"
>
<VIcon
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> </VBtn>
@@ -285,17 +260,10 @@ onMounted(() => {
accept=".jpeg,.png,.jpg,GIF" accept=".jpeg,.png,.jpg,GIF"
hidden hidden
@input="changeAvatar" @input="changeAvatar"
> />
<VBtn <VBtn type="reset" color="error" variant="tonal" @click="resetAvatar">
type="reset" <VIcon icon="mdi-refresh" />
color="error"
variant="tonal"
@click="resetAvatar"
>
<VIcon
icon="mdi-refresh"
/>
<span class="d-none d-sm-block ms-2">重置</span> <span class="d-none d-sm-block ms-2">重置</span>
</VBtn> </VBtn>
@@ -304,16 +272,12 @@ onMounted(() => {
variant="tonal" variant="tonal"
@click.stop="accountInfo.is_otp ? disableOtp() : getOtpUri()" @click.stop="accountInfo.is_otp ? disableOtp() : getOtpUri()"
> >
<VIcon <VIcon icon="mdi-account-key" />
icon="mdi-account-key" <span class="d-none d-sm-block ms-2">{{ accountInfo.is_otp ? '关闭验证' : '双重验证' }}</span>
/>
<span class="d-none d-sm-block ms-2">{{ accountInfo.is_otp ? "关闭验证" : "双重验证" }}</span>
</VBtn> </VBtn>
</div> </div>
<p class="text-body-1 mb-0"> <p class="text-body-1 mb-0">允许 JPGGIF PNG 格式 最大尺寸 800K</p>
允许 JPGGIF PNG 格式 最大尺寸 800K
</p>
</form> </form>
</VCardText> </VCardText>
@@ -324,33 +288,16 @@ onMounted(() => {
<VForm class="mt-6"> <VForm class="mt-6">
<VRow> <VRow>
<!-- 👉 Name --> <!-- 👉 Name -->
<VCol <VCol md="6" cols="12">
md="6" <VTextField v-model="accountInfo.name" readonly label="用户名" />
cols="12"
>
<VTextField
v-model="accountInfo.name"
readonly
label="用户名"
/>
</VCol> </VCol>
<!-- 👉 Email --> <!-- 👉 Email -->
<VCol <VCol cols="12" md="6">
cols="12" <VTextField v-model="accountInfo.email" label="邮箱" type="email" />
md="6"
>
<VTextField
v-model="accountInfo.email"
label="邮箱"
type="email"
/>
</VCol> </VCol>
<VCol <VCol cols="12" md="6">
cols="12"
md="6"
>
<!-- 👉 new password --> <!-- 👉 new password -->
<VTextField <VTextField
v-model="newPassword" v-model="newPassword"
@@ -362,32 +309,20 @@ onMounted(() => {
/> />
</VCol> </VCol>
<VCol <VCol cols="12" md="6">
cols="12"
md="6"
>
<!-- 👉 confirm password --> <!-- 👉 confirm password -->
<VTextField <VTextField
v-model="confirmPassword" v-model="confirmPassword"
:type="isConfirmPasswordVisible ? 'text' : 'password'" :type="isConfirmPasswordVisible ? 'text' : 'password'"
:append-inner-icon=" :append-inner-icon="isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
"
label="确认新密码" label="确认新密码"
@click:append-inner=" @click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
isConfirmPasswordVisible = !isConfirmPasswordVisible
"
/> />
</VCol> </VCol>
<!-- 👉 Form Actions --> <!-- 👉 Form Actions -->
<VCol <VCol cols="12" class="d-flex flex-wrap gap-4">
cols="12" <VBtn @click="saveAccountInfo"> 保存 </VBtn>
class="d-flex flex-wrap gap-4"
>
<VBtn @click="saveAccountInfo">
保存
</VBtn>
</VCol> </VCol>
</VRow> </VRow>
</VForm> </VForm>
@@ -395,10 +330,7 @@ onMounted(() => {
</VCard> </VCard>
</VCol> </VCol>
<VCol <VCol v-if="accountInfo.is_superuser" cols="12">
v-if="accountInfo.is_superuser"
cols="12"
>
<!-- 👉 Accounts --> <!-- 👉 Accounts -->
<VCard title="所有用户"> <VCard title="所有用户">
<template #append> <template #append>
@@ -409,76 +341,38 @@ onMounted(() => {
<VTable class="text-no-wrap"> <VTable class="text-no-wrap">
<thead> <thead>
<tr> <tr>
<th scope="col"> <th scope="col">用户名</th>
用户名 <th scope="col">邮箱</th>
</th> <th scope="col">状态</th>
<th scope="col"> <th scope="col">管理员</th>
邮箱 <th scope="col" class="w-5" />
</th>
<th scope="col">
状态
</th>
<th scope="col">
管理员
</th>
<th
scope="col"
class="w-5"
/>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr v-for="user in allUsers" :key="user.name">
v-for="user in allUsers"
:key="user.name"
>
<td> <td>
{{ user.name }} {{ user.name }}
</td> </td>
<td>{{ user.email }}</td> <td>{{ user.email }}</td>
<td> <td>
<VChip <VChip v-if="user.is_active" color="success" text-color="white"> 激活 </VChip>
v-if="user.is_active" <VChip v-else color="error" text-color="white"> 冻结 </VChip>
color="success"
text-color="white"
>
激活
</VChip>
<VChip
v-else
color="error"
text-color="white"
>
冻结
</VChip>
</td> </td>
<td>{{ user.is_superuser ? "是" : "否" }}</td> <td>{{ user.is_superuser ? '是' : '否' }}</td>
<td> <td>
<IconBtn v-show="accountInfo.is_superuser && accountInfo.name !== user.name"> <IconBtn v-show="accountInfo.is_superuser && accountInfo.name !== user.name">
<VIcon icon="mdi-dots-vertical" /> <VIcon icon="mdi-dots-vertical" />
<VMenu <VMenu activator="parent" close-on-content-click>
activator="parent"
close-on-content-click
>
<VList> <VList>
<VListItem <VListItem variant="plain" @click="deactivateUser(user)">
variant="plain"
@click="deactivateUser(user)"
>
<template #prepend> <template #prepend>
<VIcon icon="mdi-lock" /> <VIcon icon="mdi-lock" />
</template> </template>
<VListItemTitle> <VListItemTitle>
{{ {{ user.is_active ? '冻结' : '解冻' }}
user.is_active ? "冻结" : "解冻"
}}
</VListItemTitle> </VListItemTitle>
</VListItem> </VListItem>
<VListItem <VListItem variant="plain" base-color="error" @click="deleteUser(user)">
variant="plain"
base-color="error"
@click="deleteUser(user)"
>
<template #prepend> <template #prepend>
<VIcon icon="mdi-delete" /> <VIcon icon="mdi-delete" />
</template> </template>
@@ -495,85 +389,50 @@ onMounted(() => {
</VCol> </VCol>
</VRow> </VRow>
<!-- =弹窗 --> <!-- =弹窗 -->
<VDialog <VDialog v-model="addUserDialog" max-width="50rem" persistent z-index="1010">
v-model="addUserDialog"
max-width="50rem"
persistent
z-index="1010"
>
<!-- Dialog Content --> <!-- Dialog Content -->
<VCard title="新增用户"> <VCard title="新增用户">
<VCardText> <VCardText>
<VForm @submit.prevent="() => {}"> <VForm @submit.prevent="() => {}">
<VRow> <VRow>
<VCol <VCol cols="12" md="6">
cols="12" <VTextField v-model="userForm.name" label="用户名" :rules="[requiredValidator]" />
md="6"
>
<VTextField
v-model="userForm.name"
label="用户名"
:rules="[requiredValidator]"
/>
</VCol> </VCol>
<VCol <VCol cols="12" md="6">
cols="12"
md="6"
>
<VTextField <VTextField
v-model="userForm.password" v-model="userForm.password"
label="密码" label="密码"
:rules="[requiredValidator]" :rules="[requiredValidator]"
: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'
"
@click:append-inner="isPasswordVisible = !isPasswordVisible" @click:append-inner="isPasswordVisible = !isPasswordVisible"
/> />
</VCol> </VCol>
<VCol <VCol cols="12" md="6">
cols="12" <VTextField v-model="userForm.email" :rules="[requiredValidator]" label="邮箱" />
md="6"
>
<VTextField
v-model="userForm.email"
:rules="[requiredValidator]"
label="邮箱"
/>
</VCol> </VCol>
</VRow> </VRow>
</VForm> </VForm>
</VCardText> </VCardText>
<VCardActions> <VCardActions>
<VBtn @click="addUserDialog = false"> <VBtn @click="addUserDialog = false"> 取消 </VBtn>
取消
</VBtn>
<VSpacer /> <VSpacer />
<VBtn @click="addUser"> <VBtn @click="addUser"> 确定 </VBtn>
确定
</VBtn>
</VCardActions> </VCardActions>
</VCard> </VCard>
</VDialog> </VDialog>
<!-- 双重验证弹窗 --> <!-- 双重验证弹窗 -->
<VDialog <VDialog v-model="otpDialog" max-width="45rem" persistent z-index="1010">
v-model="otpDialog"
max-width="45rem"
persistent
z-index="1010"
>
<!-- 开启双重验证弹窗内容 --> <!-- 开启双重验证弹窗内容 -->
<VCard> <VCard>
<DialogCloseBtn @click="otpDialog = false" /> <DialogCloseBtn @click="otpDialog = false" />
<VCardText> <VCardText>
<h4 class="text-h4 text-center mb-6 mt-5"> <h4 class="text-h4 text-center mb-6 mt-5">登录双重验证</h4>
登录双重验证 <h5 class="text-h5 font-weight-medium mb-2">身份验证器</h5>
</h4><h5 class="text-h5 font-weight-medium mb-2">
身份验证器
</h5>
<p class="mb-6"> <p class="mb-6">
使用像Google AuthenticatorMicrosoft AuthenticatorAuthy或1Password这样的身份验证器应用程序扫描二维码它将为您生成一个6位数的代码供您在下方输入 使用像Google AuthenticatorMicrosoft
AuthenticatorAuthy或1Password这样的身份验证器应用程序扫描二维码它将为您生成一个6位数的代码供您在下方输入
</p> </p>
<div class="my-6"> <div class="my-6">
<QrcodeVue class="mx-auto" :value="qrCode" :size="200" max-width="25rem" /> <QrcodeVue class="mx-auto" :value="qrCode" :size="200" max-width="25rem" />
@@ -597,14 +456,12 @@ onMounted(() => {
variant="outlined" variant="outlined"
/> />
<div class="d-flex justify-end flex-wrap gap-4"> <div class="d-flex justify-end flex-wrap gap-4">
<VBtn variant="outlined" color="secondary" @click="otpDialog = false"> <VBtn variant="outlined" color="secondary" @click="otpDialog = false"> 取消 </VBtn>
取消
</VBtn>
<VBtn @click="judgeOtpPassword"> <VBtn @click="judgeOtpPassword">
确定 <template #prepend>
<template #append>
<VIcon icon="mdi-check" /> <VIcon icon="mdi-check" />
</template> </template>
确定
</VBtn> </VBtn>
</div> </div>
</VForm> </VForm>