仪表板组件支持拖动排序

This commit is contained in:
jxxghp
2024-05-09 14:45:12 +08:00
parent 74e96980e6
commit 2065992b17
18 changed files with 437 additions and 464 deletions

View File

@@ -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>

View File

@@ -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 }

View 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>

View File

@@ -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>

View File

@@ -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 ?? '/')
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>