mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-25 18:29:44 +08:00
仪表板组件支持拖动排序
This commit is contained in:
20
src/App.vue
20
src/App.vue
@@ -34,30 +34,10 @@ function startSSEMessager() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载用户监控面板配置
|
||||
async function loadDashboardConfig() {
|
||||
const response = await api.get('/user/config/Dashboard')
|
||||
if (response && response.data && response.data.value) {
|
||||
const data = JSON.stringify(response.data.value)
|
||||
if (data != localStorage.getItem('MP_DASHBOARD')) {
|
||||
localStorage.setItem('MP_DASHBOARD', data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试加载用户监控面板配置(本地无配置时才加载)
|
||||
async function tryLoadDashboardConfig() {
|
||||
if (localStorage.getItem('MP_DASHBOARD')) {
|
||||
return
|
||||
}
|
||||
await loadDashboardConfig()
|
||||
}
|
||||
|
||||
// 页面加载时,加载当前用户数据
|
||||
onBeforeMount(async () => {
|
||||
setTheme()
|
||||
startSSEMessager()
|
||||
await tryLoadDashboardConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -445,11 +445,11 @@ export interface Plugin {
|
||||
add_time?: number
|
||||
}
|
||||
|
||||
// 插件仪表板
|
||||
export interface PluginDashboard {
|
||||
// 插件ID
|
||||
// 仪表板组件
|
||||
export interface DashboardItem {
|
||||
// ID
|
||||
id: string
|
||||
// 插件名称
|
||||
// 名称
|
||||
name: string
|
||||
// 全局配置
|
||||
attrs: { [key: string]: any }
|
||||
|
||||
44
src/components/misc/DashboardElement.vue
Normal file
44
src/components/misc/DashboardElement.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { DashboardItem } from '@/api/types'
|
||||
import AnalyticsMediaStatistic from '@/views/dashboard/AnalyticsMediaStatistic.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'
|
||||
import MediaServerLatest from '@/views/dashboard/MediaServerLatest.vue'
|
||||
import MediaServerLibrary from '@/views/dashboard/MediaServerLibrary.vue'
|
||||
import MediaServerPlaying from '@/views/dashboard/MediaServerPlaying.vue'
|
||||
import DashboardRender from '@/components/render/DashboardRender.vue'
|
||||
import { isNullOrEmptyObject } from '@/@core/utils'
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
// 仪表板配置
|
||||
config: Object as PropType<DashboardItem>,
|
||||
})
|
||||
|
||||
console.log(props.config)
|
||||
</script>
|
||||
<template>
|
||||
<!-- 系统内置的仪表板 -->
|
||||
<AnalyticsStorage v-if="config?.id === 'storage'" />
|
||||
<AnalyticsMediaStatistic v-else-if="config?.id === 'mediaStatistic'" />
|
||||
<AnalyticsWeeklyOverview v-else-if="config?.id === 'weeklyOverview'" />
|
||||
<AnalyticsSpeed v-else-if="config?.id === 'speed'" />
|
||||
<AnalyticsScheduler v-else-if="config?.id === 'scheduler'" />
|
||||
<AnalyticsCpu v-else-if="config?.id === 'cpu'" />
|
||||
<AnalyticsMemory v-else-if="config?.id === 'memory'" />
|
||||
<MediaServerLibrary v-else-if="config?.id === 'library'" />
|
||||
<MediaServerPlaying v-else-if="config?.id === 'playing'" />
|
||||
<MediaServerLatest v-else-if="config?.id === 'latest'" />
|
||||
<!-- 插件仪表板 -->
|
||||
<VCard v-else-if="!isNullOrEmptyObject(props.config)">
|
||||
<VCardItem>
|
||||
<VCardTitle class="cursor-move">{{ props.config?.name }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<VCardItem>
|
||||
<DashboardRender v-for="(item, index) in props.config?.elements" :key="index" :config="item" />
|
||||
</VCardItem>
|
||||
</VCard>
|
||||
</template>
|
||||
@@ -1,20 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import AnalyticsMediaStatistic from '@/views/dashboard/AnalyticsMediaStatistic.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'
|
||||
import MediaServerLatest from '@/views/dashboard/MediaServerLatest.vue'
|
||||
import MediaServerLibrary from '@/views/dashboard/MediaServerLibrary.vue'
|
||||
import MediaServerPlaying from '@/views/dashboard/MediaServerPlaying.vue'
|
||||
import DashboardRender from '@/components/render/DashboardRender.vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import api from '@/api'
|
||||
import { isNullOrEmptyObject } from '@/@core/utils'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { PluginDashboard } from '@/api/types'
|
||||
import { DashboardItem } from '@/api/types'
|
||||
import store from '@/store'
|
||||
import DashboardElement from '@/components/misc/DashboardElement.vue'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
@@ -22,8 +13,8 @@ const display = useDisplay()
|
||||
// 从Vuex Store中获取superuser信息
|
||||
const superUser = store.state.auth.superUser
|
||||
|
||||
// 从localStorage中获取数据
|
||||
const default_config = {
|
||||
// 仪表板启用配置
|
||||
const enableConfig = ref<{ [key: string]: boolean }>({
|
||||
mediaStatistic: true,
|
||||
scheduler: false,
|
||||
speed: false,
|
||||
@@ -34,47 +25,149 @@ const default_config = {
|
||||
library: true,
|
||||
playing: true,
|
||||
latest: true,
|
||||
}
|
||||
|
||||
// 仪表盘字典
|
||||
const dashboard_names = ref<{ [key: string]: string }>({
|
||||
storage: '存储空间',
|
||||
mediaStatistic: '媒体统计',
|
||||
weeklyOverview: '最近入库',
|
||||
speed: '实时速率',
|
||||
scheduler: '后台任务',
|
||||
cpu: 'CPU',
|
||||
memory: '内存',
|
||||
library: '我的媒体库',
|
||||
playing: '继续观看',
|
||||
latest: '最近添加',
|
||||
})
|
||||
|
||||
// 有仪表板的插件
|
||||
const dashboard_plugins = ref<any[]>([])
|
||||
// 仪表板配置
|
||||
const dashboardConfigs = ref<DashboardItem[]>([
|
||||
{
|
||||
id: 'storage',
|
||||
name: '存储空间',
|
||||
attrs: {},
|
||||
cols: { cols: 12, md: 4 },
|
||||
elements: [],
|
||||
},
|
||||
{
|
||||
id: 'mediaStatistic',
|
||||
name: '媒体统计',
|
||||
attrs: {},
|
||||
cols: { cols: 12, md: 8 },
|
||||
elements: [],
|
||||
},
|
||||
{
|
||||
id: 'weeklyOverview',
|
||||
name: '最近入库',
|
||||
attrs: {},
|
||||
cols: { cols: 12, md: 4 },
|
||||
elements: [],
|
||||
},
|
||||
{
|
||||
id: 'speed',
|
||||
name: '实时速率',
|
||||
attrs: {},
|
||||
cols: { cols: 12, md: 4 },
|
||||
elements: [],
|
||||
},
|
||||
{
|
||||
id: 'scheduler',
|
||||
name: '后台任务',
|
||||
attrs: {},
|
||||
cols: { cols: 12, md: 4 },
|
||||
elements: [],
|
||||
},
|
||||
{
|
||||
id: 'cpu',
|
||||
name: 'CPU',
|
||||
attrs: {},
|
||||
cols: { cols: 12, md: 6 },
|
||||
elements: [],
|
||||
},
|
||||
{
|
||||
id: 'memory',
|
||||
name: '内存',
|
||||
attrs: {},
|
||||
cols: { cols: 12, md: 6 },
|
||||
elements: [],
|
||||
},
|
||||
{
|
||||
id: 'library',
|
||||
name: '我的媒体库',
|
||||
attrs: {},
|
||||
cols: { cols: 12 },
|
||||
elements: [],
|
||||
},
|
||||
{
|
||||
id: 'playing',
|
||||
name: '继续观看',
|
||||
attrs: {},
|
||||
cols: { cols: 12 },
|
||||
elements: [],
|
||||
},
|
||||
{
|
||||
id: 'latest',
|
||||
name: '最近添加',
|
||||
attrs: {},
|
||||
cols: { cols: 12 },
|
||||
elements: [],
|
||||
},
|
||||
])
|
||||
|
||||
// 插件仪表板配置
|
||||
const plugin_dashboards = ref<PluginDashboard[]>([])
|
||||
// 有仪表板的插件
|
||||
const dashboardPlugins = ref<any[]>([])
|
||||
|
||||
// 弹窗
|
||||
const dialog = ref(false)
|
||||
|
||||
// 初始化默认值
|
||||
const config = ref(JSON.parse(localStorage.getItem('MP_DASHBOARD') || '{}'))
|
||||
if (isNullOrEmptyObject(config.value)) {
|
||||
config.value = default_config
|
||||
// 加载用户监控面板配置(本地无配置时才加载)
|
||||
async function loadDashboardConfig() {
|
||||
// 显示配置
|
||||
const local_enable = localStorage.getItem('MP_DASHBOARD')
|
||||
if (local_enable) {
|
||||
enableConfig.value = JSON.parse(local_enable)
|
||||
} else {
|
||||
const response = await api.get('/user/config/Dashboard')
|
||||
if (response && response.data && response.data.value) {
|
||||
enableConfig.value = response.data.value
|
||||
localStorage.setItem('MP_DASHBOARD', JSON.stringify(response.data.value))
|
||||
}
|
||||
}
|
||||
// 顺序配置
|
||||
const local_order = localStorage.getItem('MP_DASHBOARD_ORDER')
|
||||
let order = null
|
||||
if (local_order) {
|
||||
order = JSON.parse(local_order)
|
||||
} else {
|
||||
const response2 = await api.get('/user/config/DashboardOrder')
|
||||
if (response2 && response2.data && response2.data.value) {
|
||||
order = response2.data.value
|
||||
localStorage.setItem('MP_DASHBOARD_ORDER', JSON.stringify(order))
|
||||
}
|
||||
}
|
||||
// 按order的顺序对dashboardConfigs进行排序
|
||||
if (order) {
|
||||
const newConfigs: DashboardItem[] = []
|
||||
order.forEach((item: { id: string }) => {
|
||||
const config = dashboardConfigs.value.find(c => c.id === item.id)
|
||||
if (config) {
|
||||
newConfigs.push(config)
|
||||
}
|
||||
})
|
||||
dashboardConfigs.value = newConfigs
|
||||
}
|
||||
}
|
||||
|
||||
// 设置项目
|
||||
function setDashboardConfig() {
|
||||
const data = JSON.stringify(config.value)
|
||||
// 启用配置
|
||||
const data = JSON.stringify(enableConfig.value)
|
||||
localStorage.setItem('MP_DASHBOARD', data)
|
||||
// 顺序配置,从dashboardConfigs中提取
|
||||
const order = JSON.stringify(dashboardConfigs.value.map(item => ({ id: item.id })))
|
||||
localStorage.setItem('MP_DASHBOARD_ORDER', order)
|
||||
// 保存到服务端
|
||||
api.post('/user/config/Dashboard', data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
try {
|
||||
api.post('/user/config/Dashboard', data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
api.post('/user/config/DashboardOrder', order, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
dialog.value = false
|
||||
}
|
||||
|
||||
@@ -83,14 +176,10 @@ async function getDashboardPlugins() {
|
||||
// 只有超级用户才能获取插件仪表板
|
||||
if (!superUser) return
|
||||
try {
|
||||
dashboard_plugins.value = await api.get('/plugin/dashboards')
|
||||
if (!isNullOrEmptyObject(dashboard_plugins.value)) {
|
||||
// 获取id和name补充到 dashboard_names 中
|
||||
dashboard_plugins.value.forEach((plugin: { [key: string]: string }) => {
|
||||
dashboard_names.value[plugin.id] = plugin.name
|
||||
})
|
||||
dashboardPlugins.value = await api.get('/plugin/dashboards')
|
||||
if (!isNullOrEmptyObject(dashboardPlugins.value)) {
|
||||
// 下载插件仪表板配置
|
||||
dashboard_plugins.value.forEach(async (plugin: { id: string }) => {
|
||||
dashboardPlugins.value.forEach(async (plugin: { id: string }) => {
|
||||
await getPluginDashboard(plugin.id)
|
||||
})
|
||||
}
|
||||
@@ -104,9 +193,10 @@ async function getPluginDashboard(id: string) {
|
||||
try {
|
||||
api.get(`/plugin/dashboard/${id}`).then((res: any) => {
|
||||
if (res) {
|
||||
plugin_dashboards.value.push(res)
|
||||
// 保存到仪表板配置中
|
||||
dashboardConfigs.value.push(res)
|
||||
// 定时刷新
|
||||
if (res.attrs?.refresh) {
|
||||
// 定时刷新
|
||||
setTimeout(() => {
|
||||
getPluginDashboard(id)
|
||||
}, res.attrs.refresh * 1000)
|
||||
@@ -118,58 +208,36 @@ async function getPluginDashboard(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 拖动排序结束
|
||||
function dragOrderEnd() {
|
||||
// 保存数据
|
||||
setDashboardConfig()
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await loadDashboardConfig()
|
||||
getDashboardPlugins()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 底部操作按钮 -->
|
||||
<VFab icon="mdi-view-dashboard-edit" location="bottom end" size="x-large" fixed app appear @click="dialog = true" />
|
||||
<!-- 仪表板 -->
|
||||
<VRow class="match-height">
|
||||
<!-- 系统内置的仪表板 -->
|
||||
<VCol v-if="config.storage" cols="12" md="4">
|
||||
<AnalyticsStorage />
|
||||
</VCol>
|
||||
<VCol v-if="config.mediaStatistic" cols="12" md="8">
|
||||
<AnalyticsMediaStatistic />
|
||||
</VCol>
|
||||
<VCol v-if="config.weeklyOverview" cols="12" md="4">
|
||||
<AnalyticsWeeklyOverview />
|
||||
</VCol>
|
||||
<VCol v-if="config.speed" cols="12" md="4">
|
||||
<AnalyticsSpeed />
|
||||
</VCol>
|
||||
<VCol v-if="config.scheduler" cols="12" md="4">
|
||||
<AnalyticsScheduler />
|
||||
</VCol>
|
||||
<VCol v-if="config.cpu" cols="12" md="6">
|
||||
<AnalyticsCpu />
|
||||
</VCol>
|
||||
<VCol v-if="config.memory" cols="12" md="6">
|
||||
<AnalyticsMemory />
|
||||
</VCol>
|
||||
<VCol v-if="config.library" cols="12">
|
||||
<MediaServerLibrary />
|
||||
</VCol>
|
||||
<VCol v-if="config.playing" cols="12">
|
||||
<MediaServerPlaying />
|
||||
</VCol>
|
||||
<VCol v-if="config.latest" cols="12">
|
||||
<MediaServerLatest />
|
||||
</VCol>
|
||||
<!-- 插件仪表板 -->
|
||||
<template v-for="plugin in plugin_dashboards" :key="plugin.id">
|
||||
<VCol v-if="config[plugin.id]" v-bind="plugin.cols">
|
||||
<VCard :title="plugin.name">
|
||||
<VCardItem>
|
||||
<DashboardRender v-for="(item, index) in plugin.elements" :key="index" :config="item" />
|
||||
</VCardItem>
|
||||
</VCard>
|
||||
<draggable
|
||||
v-model="dashboardConfigs"
|
||||
@end="dragOrderEnd"
|
||||
item-key="id"
|
||||
tag="VRow"
|
||||
:component-data="{ 'class': 'match-height' }"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<VCol v-if="enableConfig[element.id]" v-bind:="element.cols">
|
||||
<DashboardElement :config="element" />
|
||||
</VCol>
|
||||
</template>
|
||||
</VRow>
|
||||
</draggable>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<VFab icon="mdi-view-dashboard-edit" location="bottom end" size="x-large" fixed app appear @click="dialog = true" />
|
||||
|
||||
<!-- 弹窗,根据配置生成选项 -->
|
||||
<VDialog v-model="dialog" max-width="35rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
@@ -180,8 +248,8 @@ onMounted(() => {
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol v-for="(name, key) in dashboard_names" :key="key" cols="12" md="4" sm="4">
|
||||
<VCheckbox v-model="config[key]" :label="name" />
|
||||
<VCol v-for="item in dashboardConfigs" :key="item.id" cols="12" md="4" sm="4">
|
||||
<VCheckbox v-model="enableConfig[item.id]" :label="item.name" />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
|
||||
@@ -70,25 +70,6 @@ const fetchOTP = debounce(async () => {
|
||||
})
|
||||
}, 500)
|
||||
|
||||
// 加载用户监控面板配置
|
||||
async function loadDashboardConfig() {
|
||||
const response = await api.get('/user/config/Dashboard')
|
||||
if (response && response.data && response.data.value) {
|
||||
const data = JSON.stringify(response.data.value)
|
||||
if (data != localStorage.getItem('MP_DASHBOARD')) {
|
||||
localStorage.setItem('MP_DASHBOARD', data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试加载用户监控面板配置(本地无配置时才加载)
|
||||
async function tryLoadDashboardConfig() {
|
||||
if (localStorage.getItem('MP_DASHBOARD')) {
|
||||
return
|
||||
}
|
||||
await loadDashboardConfig()
|
||||
}
|
||||
|
||||
// 获取用户主题配置
|
||||
async function fetchThemeConfig() {
|
||||
const response = await api.get('/user/config/theme')
|
||||
@@ -111,8 +92,6 @@ async function setTheme() {
|
||||
async function afterLogin() {
|
||||
// 生效主题配置
|
||||
await setTheme()
|
||||
// 尝试加载用户监控面板配置(本地无配置时才加载)
|
||||
await tryLoadDashboardConfig()
|
||||
// 跳转到首页或回原始页面
|
||||
router.push(store.state.auth.originalPath ?? '/')
|
||||
}
|
||||
|
||||
@@ -6,8 +6,14 @@ 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)
|
||||
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
|
||||
@@ -22,83 +28,86 @@ const series = ref([
|
||||
// 当前值
|
||||
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,
|
||||
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: {
|
||||
lines: { show: false },
|
||||
labels: { show: false },
|
||||
axisTicks: { show: false },
|
||||
axisBorder: { show: false },
|
||||
},
|
||||
yaxis: {
|
||||
lines: { show: true },
|
||||
labels: { show: false },
|
||||
max: 100,
|
||||
},
|
||||
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
|
||||
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) {
|
||||
if (series.value[0].data.length > 30) series.value[0].data.shift()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCpuUsage()// 启动定时器
|
||||
getCpuUsage() // 启动定时器
|
||||
refreshTimer = setInterval(() => {
|
||||
getCpuUsage()
|
||||
}, 2000)
|
||||
@@ -115,20 +124,13 @@ onUnmounted(() => {
|
||||
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle class="cursor-move">CPU</VCardTitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<h6 class="text-h6">
|
||||
CPU
|
||||
</h6>
|
||||
<VueApexCharts
|
||||
type="line"
|
||||
:options="chartOptions"
|
||||
:series="series"
|
||||
:height="150"
|
||||
/>
|
||||
<VueApexCharts type="line" :options="chartOptions" :series="series" :height="150" />
|
||||
|
||||
<p class="text-center font-weight-medium mb-0">
|
||||
当前:{{ current }}%
|
||||
</p>
|
||||
<p class="text-center font-weight-medium mb-0">当前:{{ current }}%</p>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
@@ -42,8 +42,7 @@ async function loadMediaStatistic() {
|
||||
color: 'info',
|
||||
},
|
||||
]
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@@ -56,29 +55,16 @@ onMounted(() => {
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>媒体统计</VCardTitle>
|
||||
<VCardTitle class="cursor-move">媒体统计</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
v-for="item in statistics"
|
||||
:key="item.title"
|
||||
cols="6"
|
||||
sm="3"
|
||||
>
|
||||
<VCol v-for="item in statistics" :key="item.title" cols="6" sm="3">
|
||||
<div class="d-flex align-center">
|
||||
<div class="me-3">
|
||||
<VAvatar
|
||||
:color="item.color"
|
||||
rounded
|
||||
size="42"
|
||||
class="elevation-1"
|
||||
>
|
||||
<VIcon
|
||||
size="24"
|
||||
:icon="item.icon"
|
||||
/>
|
||||
<VAvatar :color="item.color" rounded size="42" class="elevation-1">
|
||||
<VIcon size="24" :icon="item.icon" />
|
||||
</VAvatar>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,8 +7,14 @@ 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)
|
||||
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
|
||||
@@ -25,79 +31,82 @@ const usedMemory = ref(0)
|
||||
// 内存使用百分比
|
||||
const memoryUsage = 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,
|
||||
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: {
|
||||
lines: { show: false },
|
||||
labels: { show: false },
|
||||
axisTicks: { show: false },
|
||||
axisBorder: { show: false },
|
||||
},
|
||||
yaxis: {
|
||||
lines: { show: true },
|
||||
labels: { show: false },
|
||||
max: 100,
|
||||
},
|
||||
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 },
|
||||
max: 100,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// 调用API接口获取最新内存使用量
|
||||
async function getMemorgUsage() {
|
||||
try {
|
||||
// 请求数据
|
||||
[usedMemory.value, memoryUsage.value] = await api.get('dashboard/memory')
|
||||
;[usedMemory.value, memoryUsage.value] = await api.get('dashboard/memory')
|
||||
series.value[0].data.push(memoryUsage.value)
|
||||
// 序列超过30条记录时,清掉前面的
|
||||
if (series.value[0].data.length > 30)
|
||||
series.value[0].data.shift()
|
||||
}
|
||||
catch (e) {
|
||||
if (series.value[0].data.length > 30) series.value[0].data.shift()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@@ -121,20 +130,13 @@ onUnmounted(() => {
|
||||
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle class="cursor-move">内存</VCardTitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<h6 class="text-h6">
|
||||
内存
|
||||
</h6>
|
||||
<VueApexCharts
|
||||
type="area"
|
||||
:options="chartOptions"
|
||||
:series="series"
|
||||
:height="150"
|
||||
/>
|
||||
<VueApexCharts type="area" :options="chartOptions" :series="series" :height="150" />
|
||||
|
||||
<p class="text-center font-weight-medium mb-0">
|
||||
当前:{{ formatBytes(usedMemory) }}
|
||||
</p>
|
||||
<p class="text-center font-weight-medium mb-0">当前:{{ formatBytes(usedMemory) }}</p>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
@@ -18,8 +18,7 @@ async function loadProcessList() {
|
||||
const res: Process[] = await api.get('dashboard/processes')
|
||||
|
||||
processList.value = res
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@@ -43,47 +42,29 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard title="系统进程">
|
||||
<VTable
|
||||
item-key="fullName"
|
||||
class="table-rounded"
|
||||
hide-default-footer
|
||||
disable-sort
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle class="cursor-move">系统进程</VCardTitle>
|
||||
</VCardItem>
|
||||
<VTable item-key="fullName" class="table-rounded" hide-default-footer disable-sort>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
v-for="header in headers"
|
||||
:id="header"
|
||||
:key="header"
|
||||
>
|
||||
<th v-for="header in headers" :id="header" :key="header">
|
||||
{{ header }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="row in processList"
|
||||
:key="row.pid"
|
||||
>
|
||||
<td
|
||||
class="text-sm"
|
||||
v-text="row.pid"
|
||||
/>
|
||||
<tr v-for="row in processList" :key="row.pid">
|
||||
<td class="text-sm" v-text="row.pid" />
|
||||
<!-- name -->
|
||||
<td>
|
||||
<h6 class="text-sm font-weight-medium">
|
||||
{{ row.name }}
|
||||
</h6>
|
||||
</td>
|
||||
<td
|
||||
class="text-sm"
|
||||
v-text="formatSeconds(row.run_time)"
|
||||
/>
|
||||
<td
|
||||
class="text-sm"
|
||||
v-text="`${row.memory} MB`"
|
||||
/>
|
||||
<td class="text-sm" v-text="formatSeconds(row.run_time)" />
|
||||
<td class="text-sm" v-text="`${row.memory} MB`" />
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
|
||||
@@ -14,8 +14,7 @@ async function loadSchedulerList() {
|
||||
const res: ScheduleInfo[] = await api.get('dashboard/schedule')
|
||||
|
||||
schedulerList.value = res
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@@ -41,25 +40,14 @@ onUnmounted(() => {
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>后台任务</VCardTitle>
|
||||
<VCardTitle class="cursor-move">后台任务</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<VList
|
||||
class="card-list"
|
||||
height="250"
|
||||
>
|
||||
<VListItem
|
||||
v-for="item in schedulerList"
|
||||
:key="item.id"
|
||||
>
|
||||
<VList class="card-list" height="250">
|
||||
<VListItem v-for="item in schedulerList" :key="item.id">
|
||||
<template #prepend>
|
||||
<VAvatar
|
||||
size="40"
|
||||
variant="tonal"
|
||||
color=""
|
||||
class="me-3"
|
||||
>
|
||||
<VAvatar size="40" variant="tonal" color="" class="me-3">
|
||||
{{ item.name[0] }}
|
||||
</VAvatar>
|
||||
</template>
|
||||
@@ -81,9 +69,7 @@ onUnmounted(() => {
|
||||
</template>
|
||||
</VListItem>
|
||||
<VListItem v-if="schedulerList.length === 0">
|
||||
<VListItemTitle class="text-center">
|
||||
没有后台服务
|
||||
</VListItemTitle>
|
||||
<VListItemTitle class="text-center"> 没有后台服务 </VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VCardText>
|
||||
|
||||
@@ -56,8 +56,7 @@ async function loadDownloaderInfo() {
|
||||
amount: formatFileSize(res.free_space),
|
||||
},
|
||||
]
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@@ -83,28 +82,18 @@ onUnmounted(() => {
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>实时速率</VCardTitle>
|
||||
<VCardTitle class="cursor-move">实时速率</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText class="pt-4">
|
||||
<div>
|
||||
<p class="text-h5 me-2">
|
||||
↑{{ formatFileSize(downloadInfo.upload_speed) }}/s
|
||||
</p>
|
||||
<p class="text-h4 me-2">
|
||||
↓{{ formatFileSize(downloadInfo.download_speed) }}/s
|
||||
</p>
|
||||
<p class="text-h5 me-2">↑{{ formatFileSize(downloadInfo.upload_speed) }}/s</p>
|
||||
<p class="text-h4 me-2">↓{{ formatFileSize(downloadInfo.download_speed) }}/s</p>
|
||||
</div>
|
||||
<VList class="card-list mt-9">
|
||||
<VListItem
|
||||
v-for="item in infoItems"
|
||||
:key="item.title"
|
||||
>
|
||||
<VListItem v-for="item in infoItems" :key="item.title">
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
rounded
|
||||
:icon="item.avatar"
|
||||
/>
|
||||
<VIcon rounded :icon="item.avatar" />
|
||||
</template>
|
||||
|
||||
<VListItemTitle class="text-sm font-weight-medium mb-1">
|
||||
|
||||
@@ -8,9 +8,7 @@ import triangleLight from '@images/misc/triangle-light.png'
|
||||
|
||||
const { global } = useTheme()
|
||||
|
||||
const triangleBg = computed(() =>
|
||||
global.name.value === 'light' ? triangleLight : triangleDark,
|
||||
)
|
||||
const triangleBg = computed(() => (global.name.value === 'light' ? triangleLight : triangleDark))
|
||||
|
||||
// 总存储空间
|
||||
const storage = ref(0)
|
||||
@@ -30,8 +28,7 @@ async function getStorage() {
|
||||
|
||||
storage.value = res.total_storage
|
||||
used.value = res.used_storage
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@@ -42,42 +39,30 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard
|
||||
title="存储空间"
|
||||
subtitle=""
|
||||
class="position-relative"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle class="cursor-move">存储空间</VCardTitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<h5 class="text-2xl font-weight-medium text-primary">
|
||||
{{ formatFileSize(storage) }}
|
||||
</h5>
|
||||
<p class="mt-2">
|
||||
已使用 {{ usedPercent }}% 🚀
|
||||
</p>
|
||||
<p class="mt-2">已使用 {{ usedPercent }}% 🚀</p>
|
||||
<p class="mt-1">
|
||||
<VProgressLinear
|
||||
:model-value="usedPercent"
|
||||
color="primary"
|
||||
/>
|
||||
<VProgressLinear :model-value="usedPercent" color="primary" />
|
||||
</p>
|
||||
</VCardText>
|
||||
|
||||
<!-- Triangle Background -->
|
||||
<VImg
|
||||
:src="triangleBg"
|
||||
class="triangle-bg flip-in-rtl"
|
||||
/>
|
||||
<VImg :src="triangleBg" class="triangle-bg flip-in-rtl" />
|
||||
|
||||
<!-- Trophy -->
|
||||
<VImg
|
||||
:src="trophy"
|
||||
class="trophy"
|
||||
/>
|
||||
<VImg :src="trophy" class="trophy" />
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@layouts/styles/mixins" as layoutsMixins;
|
||||
@use '@layouts/styles/mixins' as layoutsMixins;
|
||||
|
||||
.v-card .triangle-bg {
|
||||
position: absolute;
|
||||
|
||||
@@ -80,8 +80,7 @@ const options = controlledComputed(
|
||||
fontSize: '12px',
|
||||
},
|
||||
|
||||
formatter: (value: number) =>
|
||||
value > 999 ? (value / 1000).toFixed(0) : value,
|
||||
formatter: (value: number) => (value > 999 ? (value / 1000).toFixed(0) : value),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -100,8 +99,7 @@ async function getWeeklyData() {
|
||||
const res: number[] = await api.get('dashboard/transfer')
|
||||
|
||||
series.value = [{ data: res }]
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@@ -114,16 +112,11 @@ onMounted(() => {
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>最近入库</VCardTitle>
|
||||
<VCardTitle class="cursor-move">最近入库</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<VueApexCharts
|
||||
type="bar"
|
||||
:options="options"
|
||||
:series="series"
|
||||
:height="160"
|
||||
/>
|
||||
<VueApexCharts type="bar" :options="options" :series="series" :height="160" />
|
||||
|
||||
<div class="d-flex align-center mb-3">
|
||||
<h5 class="text-h5 me-4">
|
||||
@@ -132,13 +125,7 @@ onMounted(() => {
|
||||
<p>最近一周入库了 {{ totalCount }} 部影片 😎</p>
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
v-if="superUser"
|
||||
block
|
||||
to="/history"
|
||||
>
|
||||
查看详情
|
||||
</VBtn>
|
||||
<VBtn v-if="superUser" block to="/history"> 查看详情 </VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
@@ -10,8 +10,7 @@ const latestList = ref<MediaServerPlayItem[]>([])
|
||||
async function loadLatest() {
|
||||
try {
|
||||
latestList.value = await api.get('mediaserver/latest')
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@@ -24,19 +23,11 @@ onMounted(() => {
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>最近添加</VCardTitle>
|
||||
<VCardTitle class="cursor-move">最近添加</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<div
|
||||
v-if="latestList.length > 0"
|
||||
class="grid gap-4 grid-media-card mx-3 mb-3"
|
||||
tabindex="0"
|
||||
>
|
||||
<PosterCard
|
||||
v-for="data in latestList"
|
||||
:key="data.id"
|
||||
:media="data"
|
||||
/>
|
||||
<div v-if="latestList.length > 0" class="grid gap-4 grid-media-card mx-3 mb-3" tabindex="0">
|
||||
<PosterCard v-for="data in latestList" :key="data.id" :media="data" />
|
||||
</div>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
@@ -10,8 +10,7 @@ const libraryList = ref<MediaServerPlayItem[]>([])
|
||||
async function loadLibrary() {
|
||||
try {
|
||||
libraryList.value = await api.get('mediaserver/library')
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@@ -24,20 +23,11 @@ onMounted(() => {
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>我的媒体库</VCardTitle>
|
||||
<VCardTitle class="cursor-move">我的媒体库</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<div
|
||||
v-if="libraryList.length > 0"
|
||||
class="grid gap-4 grid-backdrop-card mx-3"
|
||||
tabindex="0"
|
||||
>
|
||||
<LibraryCard
|
||||
v-for="data in libraryList"
|
||||
:key="data.id"
|
||||
:media="data"
|
||||
height="10rem"
|
||||
/>
|
||||
<div v-if="libraryList.length > 0" class="grid gap-4 grid-backdrop-card mx-3" tabindex="0">
|
||||
<LibraryCard v-for="data in libraryList" :key="data.id" :media="data" height="10rem" />
|
||||
</div>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
@@ -10,8 +10,7 @@ const playingList = ref<MediaServerPlayItem[]>([])
|
||||
async function loadPlayingList() {
|
||||
try {
|
||||
playingList.value = await api.get('mediaserver/playing')
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@@ -24,20 +23,11 @@ onMounted(() => {
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>继续观看</VCardTitle>
|
||||
<VCardTitle class="cursor-move">继续观看</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<div
|
||||
v-if="playingList.length > 0"
|
||||
class="grid gap-4 grid-backdrop-card mx-3"
|
||||
tabindex="0"
|
||||
>
|
||||
<BackdropCard
|
||||
v-for="data in playingList"
|
||||
:key="data.id"
|
||||
:media="data"
|
||||
height="10rem"
|
||||
/>
|
||||
<div v-if="playingList.length > 0" class="grid gap-4 grid-backdrop-card mx-3" tabindex="0">
|
||||
<BackdropCard v-for="data in playingList" :key="data.id" :media="data" height="10rem" />
|
||||
</div>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user