Compare commits

...

10 Commits

Author SHA1 Message Date
jxxghp
09e42d5a08 build 2023-09-04 17:56:32 +08:00
jxxghp
b9a09fd1be fix 用户权限控制 2023-09-04 17:49:17 +08:00
jxxghp
b08235b9f6 fix ui 2023-09-04 12:37:34 +08:00
jxxghp
43e67893b4 fix ui 2023-09-04 12:36:05 +08:00
jxxghp
633b38da01 fix dashboard 2023-09-04 11:07:56 +08:00
jxxghp
68a4818be0 build 2023-09-04 07:08:00 +08:00
jxxghp
be3e4a7b13 fix user menu 2023-09-04 07:07:38 +08:00
jxxghp
2bc616ebbb px => rem 2023-09-03 22:36:48 +08:00
jxxghp
7058472784 feat 媒体信息组件 2023-09-03 19:25:46 +08:00
jxxghp
8d9f28b3c8 fix ui 2023-09-03 19:15:58 +08:00
21 changed files with 550 additions and 312 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "moviepilot",
"version": "1.1.3",
"version": "1.1.3-4",
"private": true,
"scripts": {
"dev": "vite --host",

View File

@@ -50,7 +50,7 @@
rgba(#{variables.$vertical-nav-background-color-rgb}, 30%) 75%,
transparent
);
block-size: calc(env(safe-area-inset-top) + 64px);
block-size: calc(env(safe-area-inset-top) + 4rem);
inline-size: 100%;
inset-block-start: calc(#{variables.$vertical-nav-header-height} - 2px);
opacity: 0;

View File

@@ -2,15 +2,15 @@
// 👉 Vertical nav
$layout-vertical-nav-z-index: 12 !default;
$layout-vertical-nav-width: 260px !default;
$layout-vertical-nav-width: 16.25rem !default;
$layout-vertical-nav-collapsed-width: 80px !default;
// 👉 Horizontal nav
$layout-horizontal-nav-z-index: 11 !default;
$layout-horizontal-nav-navbar-height: 64px !default;
$layout-horizontal-nav-navbar-height: 4rem !default;
// 👉 Navbar
$layout-vertical-nav-navbar-height: 64px !default;
$layout-vertical-nav-navbar-height: 4rem !default;
$layout-vertical-nav-navbar-is-contained: true !default;
$layout-vertical-nav-layout-navbar-z-index: 11 !default;
$layout-horizontal-nav-layout-navbar-z-index: 11 !default;
@@ -19,7 +19,7 @@ $layout-horizontal-nav-layout-navbar-z-index: 11 !default;
$layout-boxed-content-width: 1440px !default;
// 👉Footer
$layout-vertical-nav-footer-height: 56px !default;
$layout-vertical-nav-footer-height: 3.5rem !default;
// 👉 Layout overlay
$layout-overlay-z-index: 11 !default;

View File

@@ -1,10 +1,7 @@
<script lang="ts" setup>
import { useToast } from 'vue-toast-notification'
import { useTheme } from 'vuetify'
import api from './api'
import type { User } from './api/types'
import store from './store'
import avatar1 from '@images/avatars/avatar-1.png'
// 第一时间应用主题
const { global: globalTheme } = useTheme()
@@ -36,39 +33,10 @@ function startSSEMessager() {
}
}
// 当前用户信息
const accountInfo = ref<User>({
id: 0,
name: '',
password: '',
email: '',
is_active: false,
is_superuser: false,
avatar: avatar1,
})
// 调用API加载当前用户数据
async function loadAccountInfo() {
try {
const user: User = await api.get('user/current')
accountInfo.value = user
if (!accountInfo.value.avatar)
accountInfo.value.avatar = avatar1
}
catch (error) {
console.log(error)
}
}
// 页面加载时,加载当前用户数据
onBeforeMount(async () => {
await loadAccountInfo()
startSSEMessager()
})
// 提供给所有元素复用
provide('accountInfo', accountInfo)
</script>
<template>

View File

@@ -462,7 +462,7 @@ const getImgUrl: Ref<string> = computed(() => {
</VHover>
<VDialog
v-model="subscribeSeasonDialog"
max-width="600"
max-width="50rem"
content-class="whitespace-nowrap"
scrollable
>

View File

@@ -0,0 +1,147 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import type { Context } from '@/api/types'
// 输入参数
const props = defineProps({
context: Object as PropType<Context>,
})
// TMDB图片转换为w500大小
function getW500Image(url = '') {
if (!url)
return ''
return url.replace('original', 'w500')
}
// 打开TMDB详情页面
function openTmdbPage(type: string, tmdbId: number) {
if (!type || !tmdbId)
return
const url = `https://www.themoviedb.org/${type === '电影' ? 'movie' : 'tv'}/${tmdbId}`
window.open(url, '_blank')
}
</script>
<template>
<div v-show="context">
<VCol>
<div
v-if="context?.meta_info?.name"
class="d-flex justify-space-between flex-wrap flex-md-nowrap flex-column flex-md-row"
>
<div
v-if="context?.media_info?.poster_path"
class="ma-auto"
>
<VImg
width="10rem"
aspect-ratio="2/3"
class="object-cover aspect-w-2 aspect-h-3 rounded-lg ring-1 ring-gray-500"
:src="getW500Image(context?.media_info?.poster_path)"
cover
>
<template #placeholder>
<div class="w-full h-full">
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
</div>
</template>
</VImg>
</div>
<div>
<VCardItem class="pb-1">
<VCardTitle>
{{ context?.media_info?.title || context?.meta_info?.name }}
{{ context?.meta_info?.season_episode }}
</VCardTitle>
<VCardSubtitle>
{{ context?.media_info?.year || context?.meta_info?.year }}
</VCardSubtitle>
</VCardItem>
<VCardText
v-if="context?.media_info?.overview"
class="line-clamp-4 overflow-hidden text-ellipsis ..."
>
{{ context?.media_info?.overview }}
</VCardText>
<VCardItem>
<!-- 类型 -->
<VChip
v-if="context?.media_info?.type || context?.meta_info?.type"
variant="elevated"
class="me-1 mb-1 text-white bg-blue-500"
>
{{
context?.media_info?.type || context?.meta_info?.type
}}
</VChip>
<!-- 二级分类 -->
<VChip
v-if="context?.media_info?.category"
variant="elevated"
class="me-1 mb-1 text-white bg-blue-500"
>
{{ context?.media_info?.category }}
</VChip>
<!-- TMDBID -->
<VChip
v-if="context?.media_info?.tmdb_id"
variant="elevated"
color="success"
class="me-1 mb-1"
@click="openTmdbPage(context?.media_info?.type || '', context?.media_info?.tmdb_id)"
>
{{ context?.media_info?.tmdb_id }}
</VChip>
<!-- meta_info -->
<VChip
v-if="context?.meta_info?.edition"
variant="elevated"
class="me-1 mb-1 text-white bg-red-500"
>
{{ context?.meta_info?.edition }}
</VChip>
<VChip
v-if="context?.meta_info?.resource_pix"
variant="elevated"
class="me-1 mb-1 text-white bg-red-500"
>
{{ context?.meta_info?.resource_pix }}
</VChip>
<VChip
v-if="context?.meta_info?.video_encode"
variant="elevated"
class="me-1 mb-1 text-white bg-orange-500"
>
{{ context?.meta_info?.video_encode }}
</VChip>
<VChip
v-if="context?.meta_info?.audio_encode"
variant="elevated"
class="me-1 mb-1 text-white bg-orange-500"
>
{{ context?.meta_info?.audio_encode }}
</VChip>
<VChip
v-if="context?.meta_info?.resource_team"
variant="elevated"
class="me-1 mb-1 text-white bg-cyan-500"
>
{{ context?.meta_info?.resource_team }}
</VChip>
</VCardItem>
</div>
</div>
<VAlert
v-if="!context?.meta_info?.name"
icon="mdi-alert-circle-outline"
>
识别失败无法识别到有效信息
</VAlert>
</VCol>
</div>
</template>

View File

@@ -389,7 +389,7 @@ onMounted(() => {
<!-- 更新站点Cookie & UA弹窗 -->
<VDialog
v-model="siteCookieDialog"
max-width="600"
max-width="50rem"
>
<!-- Dialog Content -->
<VCard title="更新站点Cookie & UA">

View File

@@ -62,7 +62,7 @@ async function handleAddDownload(_site: any = undefined,
confirmationText: '确认',
cancellationText: '取消',
dialogProps: {
maxWidth: 600,
maxWidth: '50rem',
},
})

View File

@@ -9,6 +9,7 @@ import { formatBytes } from '@core/utils/formatters'
import type { Context, EndPoints, FileItem } from '@/api/types'
import store from '@/store'
import api from '@/api'
import MediaInfoCard from '@/components/cards/MediaInfoCard.vue'
// 输入参数
const inProps = defineProps({
@@ -87,6 +88,9 @@ const transferForm = reactive({
// 识别结果
const nameTestResult = ref<Context>()
// 识别结果对话框
const nameTestDialog = ref(false)
// 生成1到50季的下拉框选项
const seasonItems = ref(
Array.from({ length: 51 }, (_, i) => i).map(item => ({
@@ -147,7 +151,7 @@ async function deleteItem(item: FileItem) {
confirmationText: '确认',
cancellationText: '取消',
dialogProps: {
maxWidth: 600,
maxWidth: '50rem',
},
})
@@ -277,6 +281,7 @@ watch(
async () => {
items.value = []
nameTestResult.value = undefined
nameTestDialog.value = false
await load()
},
)
@@ -321,6 +326,7 @@ async function recognize(path: string) {
// 显示进度条
progressDialog.value = true
progressText.value = `正在识别 ${path} ...`
progressValue.value = 0
nameTestResult.value = await api.get('media/recognize_file', {
params: {
path,
@@ -330,24 +336,27 @@ async function recognize(path: string) {
progressDialog.value = false
if (!nameTestResult.value)
$toast.error(`${path} 识别失败!`)
nameTestDialog.value = !!nameTestResult.value?.meta_info?.name
}
catch (error) {
console.error(error)
}
}
// TMDB图片转换为w500大小
function getW500Image(url = '') {
if (!url)
return ''
return url.replace('original', 'w500')
}
// 弹出菜单
const dropdownItems = ref([
{
title: '重命名',
title: '识别',
value: 1,
props: {
prependIcon: 'mdi-text-recognition',
click: (_item: FileItem) => {
recognize(_item.path || '')
},
},
}, {
title: '重命名',
value: 2,
props: {
prependIcon: 'mdi-rename',
click: showRenmae,
@@ -355,7 +364,7 @@ const dropdownItems = ref([
},
{
title: '整理',
value: 2,
value: 3,
props: {
prependIcon: 'mdi-folder-arrow-right',
click: showTransfer,
@@ -363,7 +372,7 @@ const dropdownItems = ref([
},
{
title: '删除',
value: 3,
value: 4,
props: {
prependIcon: 'mdi-delete-outline',
color: 'error',
@@ -400,114 +409,6 @@ onMounted(() => {
class="text-center break-all"
>
文件: {{ path }}
<VDivider v-if="nameTestResult" class="my-3" />
<div
v-if="nameTestResult?.meta_info?.name"
class="d-flex justify-space-between flex-wrap flex-md-nowrap flex-column flex-md-row"
>
<div
v-if="nameTestResult?.media_info?.poster_path"
>
<VImg
width="10rem"
aspect-ratio="2/3"
class="object-cover aspect-w-2 aspect-h-3 rounded-lg ring-1 ring-gray-500"
:src="getW500Image(nameTestResult?.media_info?.poster_path)"
cover
>
<template #placeholder>
<div class="w-full h-full">
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
</div>
</template>
</VImg>
</div>
<div class="text-start grow">
<VCardItem class="pb-1">
<VCardTitle>
{{ nameTestResult?.media_info?.title || nameTestResult?.meta_info?.name }}
{{ nameTestResult?.meta_info?.season_episode }}
</VCardTitle>
<VCardSubtitle>
{{ nameTestResult?.media_info?.year || nameTestResult?.meta_info?.year }}
</VCardSubtitle>
</VCardItem>
<VCardText
v-if="nameTestResult?.media_info?.overview"
class="line-clamp-4 overflow-hidden text-ellipsis ..."
>
{{ nameTestResult?.media_info?.overview }}
</VCardText>
<VCardItem>
<!-- 类型 -->
<VChip
v-if="nameTestResult?.media_info?.type || nameTestResult?.meta_info?.type"
variant="elevated"
class="me-1 mb-1 text-white bg-blue-500"
>
{{
nameTestResult?.media_info?.type || nameTestResult?.meta_info?.type
}}
</VChip>
<!-- 二级分类 -->
<VChip
v-if="nameTestResult?.media_info?.category"
variant="elevated"
class="me-1 mb-1 text-white bg-blue-500"
>
{{ nameTestResult?.media_info?.category }}
</VChip>
<!-- TMDBID -->
<VChip
v-if="nameTestResult?.media_info?.tmdb_id"
variant="elevated"
color="success"
class="me-1 mb-1"
>
{{ nameTestResult?.media_info?.tmdb_id }}
</VChip>
<!-- meta_info -->
<VChip
v-if="nameTestResult?.meta_info?.edition"
variant="elevated"
class="me-1 mb-1 text-white bg-red-500"
>
{{ nameTestResult?.meta_info?.edition }}
</VChip>
<VChip
v-if="nameTestResult?.meta_info?.resource_pix"
variant="elevated"
class="me-1 mb-1 text-white bg-red-500"
>
{{ nameTestResult?.meta_info?.resource_pix }}
</VChip>
<VChip
v-if="nameTestResult?.meta_info?.video_encode"
variant="elevated"
class="me-1 mb-1 text-white bg-orange-500"
>
{{ nameTestResult?.meta_info?.video_encode }}
</VChip>
<VChip
v-if="nameTestResult?.meta_info?.audio_encode"
variant="elevated"
class="me-1 mb-1 text-white bg-orange-500"
>
{{ nameTestResult?.meta_info?.audio_encode }}
</VChip>
<VChip
v-if="nameTestResult?.meta_info?.resource_team"
variant="elevated"
class="me-1 mb-1 text-white bg-cyan-500"
>
{{ nameTestResult?.meta_info?.resource_team }}
</VChip>
</VCardItem>
</div>
</div>
</VCardText>
<VCardText
v-else-if="isFile && isImage"
@@ -553,6 +454,9 @@ onMounted(() => {
</VList>
</VMenu>
</IconBtn>
<IconBtn class="d-none d-sm-block" @click.stop="recognize(item.path)">
<VIcon icon="mdi-text-recognition" />
</IconBtn>
<IconBtn class="d-none d-sm-block" @click.stop="showRenmae(item)">
<VIcon icon="mdi-rename" />
</IconBtn>
@@ -606,6 +510,9 @@ onMounted(() => {
</VList>
</VMenu>
</IconBtn>
<IconBtn class="d-none d-sm-block" @click.stop="recognize(item.path)">
<VIcon icon="mdi-text-recognition" />
</IconBtn>
<IconBtn class="d-none d-sm-block" @click.stop="showRenmae(item)">
<VIcon icon="mdi-rename" />
</IconBtn>
@@ -665,7 +572,7 @@ onMounted(() => {
<!-- 重命名弹窗 -->
<VDialog
v-model="renamePopper"
max-width="600"
max-width="50rem"
>
<template #activator="{ props }">
<IconBtn title="重命名" v-bind="props">
@@ -831,6 +738,7 @@ onMounted(() => {
<vCardText class="text-center">
{{ progressText }}
<vProgressLinear
v-if="progressValue"
color="white"
class="mb-0 mt-1"
:model-value="progressValue"
@@ -838,6 +746,19 @@ onMounted(() => {
</vCardText>
</vCard>
</vDialog>
<!-- 识别结果对话框 -->
<vDialog
v-model="nameTestDialog"
:scrim="false"
width="800"
>
<vCard>
<DialogCloseBtn @click="nameTestDialog = false" />
<VCardItem>
<MediaInfoCard :context="nameTestResult" />
</VCardItem>
</vCard>
</vDialog>
</template>
<style lang="scss" scoped>

View File

@@ -128,7 +128,7 @@ async function mkdir() {
</IconBtn>
<VDialog
v-model="newFolderPopper"
max-width="600"
max-width="50rem"
>
<template #activator="{ props }">
<IconBtn title="新建文件夹" v-bind="props">

View File

@@ -9,6 +9,10 @@ import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
import SearchBar from '@/layouts/components/SearchBar.vue'
import ShortcutBar from '@/layouts/components/ShortcutBar.vue'
import UserProfile from '@/layouts/components/UserProfile.vue'
import store from '@/store'
// 从Vuex Store中获取superuser信息
const superUser = store.state.auth.superUser
</script>
<template>
@@ -87,6 +91,7 @@ import UserProfile from '@/layouts/components/UserProfile.vue'
}"
/>
<VerticalNavLink
v-if="superUser"
:item="{
title: '电影',
icon: 'mdi-movie-check-outline',
@@ -94,6 +99,7 @@ import UserProfile from '@/layouts/components/UserProfile.vue'
}"
/>
<VerticalNavLink
v-if="superUser"
:item="{
title: '电视剧',
icon: 'mdi-television-classic',
@@ -101,6 +107,7 @@ import UserProfile from '@/layouts/components/UserProfile.vue'
}"
/>
<VerticalNavLink
v-if="superUser"
:item="{
title: '自定义',
icon: 'mdi-rss',
@@ -128,6 +135,7 @@ import UserProfile from '@/layouts/components/UserProfile.vue'
}"
/>
<VerticalNavLink
v-if="superUser"
:item="{
title: '历史记录',
icon: 'mdi-history',
@@ -135,6 +143,7 @@ import UserProfile from '@/layouts/components/UserProfile.vue'
}"
/>
<VerticalNavLink
v-if="superUser"
:item="{
title: '文件管理',
icon: 'mdi-folder-multiple-outline',
@@ -144,11 +153,13 @@ import UserProfile from '@/layouts/components/UserProfile.vue'
<!-- 👉 系统 -->
<VerticalNavSectionTitle
v-if="superUser"
:item="{
heading: '系统',
}"
/>
<VerticalNavLink
v-if="superUser"
:item="{
title: '插件',
icon: 'mdi-apps',
@@ -156,6 +167,7 @@ import UserProfile from '@/layouts/components/UserProfile.vue'
}"
/>
<VerticalNavLink
v-if="superUser"
:item="{
title: '站点管理',
icon: 'mdi-web',
@@ -163,6 +175,7 @@ import UserProfile from '@/layouts/components/UserProfile.vue'
}"
/>
<VerticalNavLink
v-if="superUser"
:item="{
title: '设定',
icon: 'mdi-cog',

View File

@@ -31,7 +31,7 @@ function search() {
>
<VDialog
v-model="searchDialog"
max-width="600"
max-width="50rem"
transition="dialog-top-transition"
>
<!-- Dialog Activator -->

View File

@@ -144,7 +144,7 @@ const ruleTestDialog = ref(false)
<!-- 名称测试弹窗 -->
<VDialog
v-model="nameTestDialog"
max-width="800"
max-width="50rem"
>
<VCard title="名称识别测试">
<DialogCloseBtn @click="nameTestDialog = false" />
@@ -156,7 +156,7 @@ const ruleTestDialog = ref(false)
<!-- 网络测试弹窗 -->
<VDialog
v-model="netTestDialog"
max-width="600"
max-width="35rem"
>
<VCard title="网络测试">
<DialogCloseBtn @click="netTestDialog = false" />
@@ -168,7 +168,7 @@ const ruleTestDialog = ref(false)
<!-- 实时日志弹窗 -->
<VDialog
v-model="loggingDialog"
max-width="1280"
class="w-full lg:w-4/5"
scrollable
>
<VCard title="实时日志">
@@ -181,7 +181,7 @@ const ruleTestDialog = ref(false)
<!-- 规则测试弹窗 -->
<VDialog
v-model="ruleTestDialog"
max-width="800"
max-width="50rem"
scrollable
>
<VCard title="过滤规则测试">

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { useStore } from 'vuex'
import router from '@/router'
import avatar1 from '@images/avatars/avatar-1.png'
// Vuex Store
const store = useStore()
@@ -14,8 +15,10 @@ function logout() {
router.push('/login')
}
// 获取当前用户信息
const accountInfo: any = inject('accountInfo')
// 从Vuex Store中获取信息
const superUser = store.state.auth.superUser
const userName = store.state.auth.userName
const avatar = store.state.auth.avatar
</script>
<template>
@@ -24,7 +27,7 @@ const accountInfo: any = inject('accountInfo')
color="primary"
variant="tonal"
>
<VImg :src="accountInfo.avatar" />
<VImg :src="avatar ?? avatar1" />
<!-- SECTION Menu -->
<VMenu
@@ -42,20 +45,21 @@ const accountInfo: any = inject('accountInfo')
color="primary"
variant="tonal"
>
<VImg :src="accountInfo.avatar" />
<VImg :src="avatar ?? avatar1" />
</VAvatar>
</VListItemAction>
</template>
<VListItemTitle class="font-weight-semibold">
{{ accountInfo.is_superuser ? "管理员" : "普通用户" }}
{{ superUser ? "管理员" : "普通用户" }}
</VListItemTitle>
<VListItemSubtitle>{{ accountInfo.name }}</VListItemSubtitle>
<VListItemSubtitle>{{ userName }}</VListItemSubtitle>
</VListItem>
<VDivider class="my-2" />
<!-- 👉 Profile -->
<VListItem
v-if="superUser"
link
to="setting"
>

View File

@@ -1,10 +1,11 @@
<script setup lang="ts">
import AnalyticsMediaStatistic from '@/views/dashboard/AnalyticsMediaStatistic.vue'
import AnalyticsProcesses from '@/views/dashboard/AnalyticsProcesses.vue'
import AnalyticsScheduler from '@/views/dashboard/AnalyticsScheduler.vue'
import AnalyticsSpeed from '@/views/dashboard/AnalyticsSpeed.vue'
import AnalyticsStorage from '@/views/dashboard/AnalyticsStorage.vue'
import AnalyticsWeeklyOverview from '@/views/dashboard/AnalyticsWeeklyOverview.vue'
import AnalyticsCpu from '@/views/dashboard/AnalyticsCpu.vue'
import AnalyticsMemory from '@/views/dashboard/AnalyticsMemory.vue'
</script>
<template>
@@ -44,8 +45,18 @@ import AnalyticsWeeklyOverview from '@/views/dashboard/AnalyticsWeeklyOverview.v
<AnalyticsScheduler />
</VCol>
<VCol cols="12">
<AnalyticsProcesses />
<VCol
cols="12"
md="6"
>
<AnalyticsCpu />
</VCol>
<VCol
cols="12"
md="6"
>
<AnalyticsMemory />
</VCol>
</VRow>
</template>

View File

@@ -66,10 +66,16 @@ function login() {
.then((response: any) => {
// 获取token
const token = response.access_token
const superuser = response.super_user
const username = response.user_name
const avatar = response.avatar
// 更新token和remember状态到Vuex Store
store.dispatch('auth/updateToken', token)
store.dispatch('auth/updateRemember', form.value.remember)
store.dispatch('auth/updateSuperUser', superuser)
store.dispatch('auth/updateUserName', username)
store.dispatch('auth/updateAvatar', avatar)
// 跳转到首页
router.push('/')

View File

@@ -4,6 +4,9 @@ import type { Module } from 'vuex'
interface AuthState {
token: string | null
remember: boolean
superUser: boolean
userName: string
avatar: string
}
// 定义根状态类型
@@ -17,6 +20,9 @@ const authModule: Module<AuthState, RootState> = {
state: {
token: null,
remember: false,
superUser: false,
userName: '',
avatar: '',
},
mutations: {
setToken(state, token: string) {
@@ -28,6 +34,15 @@ const authModule: Module<AuthState, RootState> = {
setRemember(state, remember: boolean) {
state.remember = remember
},
setSuperUser(state, superUser: boolean) {
state.superUser = superUser
},
setUserName(state, userName: string) {
state.userName = userName
},
setAvatar(state, avatar: string) {
state.avatar = avatar
},
},
actions: {
updateToken({ commit }, token: string) {
@@ -39,10 +54,22 @@ const authModule: Module<AuthState, RootState> = {
updateRemember({ commit }, remember: boolean) {
commit('setRemember', remember)
},
updateSuperUser({ commit }, superUser: boolean) {
commit('setSuperUser', superUser)
},
updateUserName({ commit }, userName: string) {
commit('setUserName', userName)
},
updateAvatar({ commit }, avatar: string) {
commit('setAvatar', avatar)
},
},
getters: {
getToken: state => state.token,
getRemember: state => state.remember,
getSuperUser: state => state.superUser,
getUserName: state => state.userName,
getAvatar: state => state.avatar,
},
}

View File

@@ -0,0 +1,134 @@
<script setup lang="ts">
import VueApexCharts from 'vue3-apexcharts'
import { useTheme } from 'vuetify'
import { hexToRgb } from '@layouts/utils'
import api from '@/api'
const vuetifyTheme = useTheme()
const currentTheme = controlledComputed(() => vuetifyTheme.name.value, () => vuetifyTheme.current.value.colors)
const variableTheme = controlledComputed(() => vuetifyTheme.name.value, () => vuetifyTheme.current.value.variables)
// 定时器
let refreshTimer: NodeJS.Timer | null = null
// 时间序列
const series = ref([
{
data: [0],
},
])
// 当前值
const current = ref(0)
const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
return {
chart: {
parentHeightOffset: 0,
toolbar: { show: false },
animations: { enabled: false },
},
tooltip: { enabled: false },
grid: {
borderColor: `rgba(${hexToRgb(String(variableTheme.value['border-color']))},${variableTheme.value['border-opacity']})`,
strokeDashArray: 6,
xaxis: {
lines: { show: false },
},
yaxis: {
lines: { show: true },
},
padding: {
top: -10,
left: -7,
right: 5,
bottom: 5,
},
},
stroke: {
width: 3,
lineCap: 'butt',
curve: 'smooth',
},
colors: [currentTheme.value.primary],
markers: {
size: 6,
offsetY: 4,
offsetX: -2,
strokeWidth: 3,
colors: ['transparent'],
strokeColors: 'transparent',
discrete: [
{
size: 5.5,
seriesIndex: 0,
strokeColor: currentTheme.value.primary,
fillColor: currentTheme.value.surface,
},
],
hover: { size: 7 },
},
xaxis: {
labels: { show: false },
axisTicks: { show: false },
axisBorder: { show: false },
},
yaxis: {
labels: { show: false },
max: 100,
},
}
})
// 调用API接口获取最新CPU使用率
async function getCpuUsage() {
try {
// 请求数据
current.value = await api.get('dashboard/cpu') ?? 0
// 添加到序列
series.value[0].data.push(current.value)
// 序列超过30条记录时清掉前面的
if (series.value[0].data.length > 30)
series.value[0].data.shift()
}
catch (e) {
console.log(e)
}
}
onMounted(() => {
getCpuUsage()// 启动定时器
refreshTimer = setInterval(() => {
getCpuUsage()
}, 2000)
})
// 组件卸载时停止定时器
onUnmounted(() => {
if (refreshTimer) {
clearInterval(refreshTimer)
refreshTimer = null
}
})
</script>
<template>
<VCard>
<VCardText>
<h6 class="text-h6">
CPU
</h6>
<VueApexCharts
type="line"
:options="chartOptions"
:series="series"
:height="150"
/>
<p class="text-center font-weight-medium mb-0">
当前{{ current }}%
</p>
</VCardText>
</VCard>
</template>

View File

@@ -0,0 +1,138 @@
<script setup lang="ts">
import VueApexCharts from 'vue3-apexcharts'
import { useTheme } from 'vuetify'
import { hexToRgb } from '@layouts/utils'
import api from '@/api'
import { formatBytes } from '@/@core/utils/formatters'
const vuetifyTheme = useTheme()
const currentTheme = controlledComputed(() => vuetifyTheme.name.value, () => vuetifyTheme.current.value.colors)
const variableTheme = controlledComputed(() => vuetifyTheme.name.value, () => vuetifyTheme.current.value.variables)
// 定时器
let refreshTimer: NodeJS.Timer | null = null
// 时间序列
const series = ref([
{
data: [0],
},
])
// 当前值
const current = ref(0)
const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
return {
chart: {
parentHeightOffset: 0,
toolbar: { show: false },
animations: { enabled: false },
},
tooltip: { enabled: false },
grid: {
borderColor: `rgba(${hexToRgb(String(variableTheme.value['border-color']))},${variableTheme.value['border-opacity']})`,
strokeDashArray: 6,
xaxis: {
lines: { show: false },
},
yaxis: {
lines: { show: true },
},
padding: {
top: -10,
left: -7,
right: 5,
bottom: 5,
},
},
stroke: {
width: 3,
lineCap: 'butt',
curve: 'smooth',
},
colors: [currentTheme.value.primary],
markers: {
size: 6,
offsetY: 4,
offsetX: -2,
strokeWidth: 3,
colors: ['transparent'],
strokeColors: 'transparent',
discrete: [
{
size: 5.5,
seriesIndex: 0,
strokeColor: currentTheme.value.primary,
fillColor: currentTheme.value.surface,
},
],
hover: { size: 7 },
},
dataLabels: {
enabled: false,
},
xaxis: {
labels: { show: false },
axisTicks: { show: false },
axisBorder: { show: false },
},
yaxis: {
labels: { show: false },
},
}
})
// 调用API接口获取最新内存使用量
async function getMemorgUsage() {
try {
// 请求数据
current.value = await api.get('dashboard/memory') ?? 0
// 添加到序列
series.value[0].data.push(current.value / 1024 / 1024 / 1024)
// 序列超过30条记录时清掉前面的
if (series.value[0].data.length > 30)
series.value[0].data.shift()
}
catch (e) {
console.log(e)
}
}
onMounted(() => {
getMemorgUsage()
// 启动定时器
refreshTimer = setInterval(() => {
getMemorgUsage()
}, 3000)
})
// 组件卸载时停止定时器
onUnmounted(() => {
if (refreshTimer) {
clearInterval(refreshTimer)
refreshTimer = null
}
})
</script>
<template>
<VCard>
<VCardText>
<h6 class="text-h6">
内存
</h6>
<VueApexCharts
type="area"
:options="chartOptions"
:series="series"
:height="150"
/>
<p class="text-center font-weight-medium mb-0">
当前{{ formatBytes(current) }}
</p>
</VCardText>
</VCard>
</template>

View File

@@ -131,7 +131,7 @@ async function removeHistory(item: TransferHistory) {
confirmationText: '同步删除文件',
cancellationText: '仅删除历史记录',
dialogProps: {
maxWidth: 600,
maxWidth: '50rem',
},
confirmationButtonProps: {
color: 'error',
@@ -180,7 +180,7 @@ async function removeHistoryBatch() {
confirmationText: '同步删除文件',
cancellationText: '仅删除历史记录',
dialogProps: {
maxWidth: 600,
maxWidth: '50rem',
},
confirmationButtonProps: {
color: 'error',
@@ -393,7 +393,7 @@ const dropdownItems = ref([
</VCard>
<VDialog
v-model="redoDialog"
max-width="600"
max-width="50rem"
>
<!-- Dialog Content -->
<VCard title="重新整理">

View File

@@ -3,6 +3,7 @@ import { reactive, ref } from 'vue'
import { requiredValidator } from '@/@validators'
import api from '@/api'
import type { Context } from '@/api/types'
import MediaInfoCard from '@/components/cards/MediaInfoCard.vue'
// 识别结果
const nameTestResult = ref<Context>()
@@ -45,22 +46,6 @@ async function nameTest() {
console.error(error)
}
}
// 打开TMDB详情页面
function openTmdbPage(type: string, tmdbId: number) {
if (!type || !tmdbId)
return
const url = `https://www.themoviedb.org/${type === '电影' ? 'movie' : 'tv'}/${tmdbId}`
window.open(url, '_blank')
}
// TMDB图片转换为w500大小
function getW500Image(url = '') {
if (!url)
return ''
return url.replace('original', 'w500')
}
</script>
<template>
@@ -101,123 +86,7 @@ function getW500Image(url = '') {
</VForm>
<VExpandTransition>
<div v-show="showResult">
<VCol>
<div
v-if="nameTestResult?.meta_info?.name"
class="d-flex justify-space-between flex-wrap flex-md-nowrap flex-column flex-md-row"
>
<div
v-if="nameTestResult?.media_info?.poster_path"
class="ma-auto"
>
<VImg
width="10rem"
aspect-ratio="2/3"
class="object-cover aspect-w-2 aspect-h-3 rounded-lg ring-1 ring-gray-500"
:src="getW500Image(nameTestResult?.media_info?.poster_path)"
cover
>
<template #placeholder>
<div class="w-full h-full">
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
</div>
</template>
</VImg>
</div>
<div>
<VCardItem class="pb-1">
<VCardTitle>
{{ nameTestResult?.media_info?.title || nameTestResult?.meta_info?.name }}
{{ nameTestResult?.meta_info?.season_episode }}
</VCardTitle>
<VCardSubtitle>
{{ nameTestResult?.media_info?.year || nameTestResult?.meta_info?.year }}
</VCardSubtitle>
</VCardItem>
<VCardText
v-if="nameTestResult?.media_info?.overview"
class="line-clamp-4 overflow-hidden text-ellipsis ..."
>
{{ nameTestResult?.media_info?.overview }}
</VCardText>
<VCardItem>
<!-- 类型 -->
<VChip
v-if="nameTestResult?.media_info?.type || nameTestResult?.meta_info?.type"
variant="elevated"
class="me-1 mb-1 text-white bg-blue-500"
>
{{
nameTestResult?.media_info?.type || nameTestResult?.meta_info?.type
}}
</VChip>
<!-- 二级分类 -->
<VChip
v-if="nameTestResult?.media_info?.category"
variant="elevated"
class="me-1 mb-1 text-white bg-blue-500"
>
{{ nameTestResult?.media_info?.category }}
</VChip>
<!-- TMDBID -->
<VChip
v-if="nameTestResult?.media_info?.tmdb_id"
variant="elevated"
color="success"
class="me-1 mb-1"
@click="openTmdbPage(nameTestResult?.media_info?.type || '', nameTestResult?.media_info?.tmdb_id)"
>
{{ nameTestResult?.media_info?.tmdb_id }}
</VChip>
<!-- meta_info -->
<VChip
v-if="nameTestResult?.meta_info?.edition"
variant="elevated"
class="me-1 mb-1 text-white bg-red-500"
>
{{ nameTestResult?.meta_info?.edition }}
</VChip>
<VChip
v-if="nameTestResult?.meta_info?.resource_pix"
variant="elevated"
class="me-1 mb-1 text-white bg-red-500"
>
{{ nameTestResult?.meta_info?.resource_pix }}
</VChip>
<VChip
v-if="nameTestResult?.meta_info?.video_encode"
variant="elevated"
class="me-1 mb-1 text-white bg-orange-500"
>
{{ nameTestResult?.meta_info?.video_encode }}
</VChip>
<VChip
v-if="nameTestResult?.meta_info?.audio_encode"
variant="elevated"
class="me-1 mb-1 text-white bg-orange-500"
>
{{ nameTestResult?.meta_info?.audio_encode }}
</VChip>
<VChip
v-if="nameTestResult?.meta_info?.resource_team"
variant="elevated"
class="me-1 mb-1 text-white bg-cyan-500"
>
{{ nameTestResult?.meta_info?.resource_team }}
</VChip>
</VCardItem>
</div>
</div>
<VAlert
v-if="!nameTestResult?.meta_info?.name"
icon="mdi-alert-circle-outline"
>
识别失败无法识别到有效信息
</VAlert>
</VCol>
<MediaInfoCard :context="nameTestResult" />
</div>
</VExpandTransition>
</template>