mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-02 22:31:07 +08:00
add dashboard
This commit is contained in:
@@ -1,57 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useTheme } from 'vuetify'
|
||||
import triangleDark from '@images/misc/triangle-dark.png'
|
||||
import triangleLight from '@images/misc/triangle-light.png'
|
||||
import trophy from '@images/misc/trophy.png'
|
||||
|
||||
const { global } = useTheme()
|
||||
const triangleBg = computed(() => global.name.value === 'light' ? triangleLight : triangleDark)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard
|
||||
title="Congratulations John! 🎉"
|
||||
subtitle="Best seller of the month"
|
||||
class="position-relative"
|
||||
>
|
||||
<VCardText>
|
||||
<h5 class="text-2xl font-weight-medium text-primary">
|
||||
$42.8k
|
||||
</h5>
|
||||
<p>78% of target 🚀</p>
|
||||
<VBtn size="small">
|
||||
View Sales
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
|
||||
<!-- Triangle Background -->
|
||||
<VImg
|
||||
:src="triangleBg"
|
||||
class="triangle-bg flip-in-rtl"
|
||||
/>
|
||||
|
||||
<!-- Trophy -->
|
||||
<VImg
|
||||
:src="trophy"
|
||||
class="trophy"
|
||||
/>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@layouts/styles/mixins" as layoutsMixins;
|
||||
|
||||
.v-card .triangle-bg {
|
||||
position: absolute;
|
||||
inline-size: 10.375rem;
|
||||
inset-block-end: 0;
|
||||
inset-inline-end: 0;
|
||||
}
|
||||
|
||||
.v-card .trophy {
|
||||
position: absolute;
|
||||
inline-size: 4.9375rem;
|
||||
inset-block-end: 2rem;
|
||||
inset-inline-end: 2rem;
|
||||
}
|
||||
</style>
|
||||
87
src/views/dashboard/AnalyticsMediaStatistic.vue
Normal file
87
src/views/dashboard/AnalyticsMediaStatistic.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import api from "@/api";
|
||||
import { MediaStatistic } from "@/api/types";
|
||||
|
||||
const statistics = ref([
|
||||
{
|
||||
title: "",
|
||||
stats: "",
|
||||
icon: "",
|
||||
color: "",
|
||||
},
|
||||
]);
|
||||
|
||||
// 调用API加载媒体统计数据
|
||||
const loadMediaStatistic = async () => {
|
||||
try {
|
||||
const res: MediaStatistic = await api.get("dashboard/statistic");
|
||||
statistics.value = [
|
||||
{
|
||||
title: "电影",
|
||||
stats: res.movie_count.toLocaleString(),
|
||||
icon: "mdi-movie-roll",
|
||||
color: "primary",
|
||||
},
|
||||
{
|
||||
title: "电视剧",
|
||||
stats: res.tv_count.toLocaleString(),
|
||||
icon: "mdi-television-box",
|
||||
color: "success",
|
||||
},
|
||||
{
|
||||
title: "剧集",
|
||||
stats: res.episode_count.toLocaleString(),
|
||||
icon: "mdi-television-classic",
|
||||
color: "warning",
|
||||
},
|
||||
{
|
||||
title: "用户",
|
||||
stats: res.user_count.toLocaleString(),
|
||||
icon: "mdi-account",
|
||||
color: "info",
|
||||
},
|
||||
];
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadMediaStatistic();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>媒体统计</VCardTitle>
|
||||
|
||||
<template #append>
|
||||
<div class="me-n3">
|
||||
<MoreBtn />
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column">
|
||||
<span class="text-caption">
|
||||
{{ item.title }}
|
||||
</span>
|
||||
<span class="text-h6">{{ item.stats }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
55
src/views/dashboard/AnalyticsProcesses.vue
Normal file
55
src/views/dashboard/AnalyticsProcesses.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script lang="ts" setup>
|
||||
import { formatSeconds } from "@/@core/utils/formatters";
|
||||
import api from "@/api";
|
||||
import { Process } from "@/api/types";
|
||||
|
||||
const headers = ["进程ID", "进程名称", "运行时间", "内存占用"];
|
||||
|
||||
const processList = ref<Process[]>([]);
|
||||
|
||||
// 调用API加载数据
|
||||
const loadProcessList = async () => {
|
||||
try {
|
||||
const res: Process[] = await api.get("dashboard/processes");
|
||||
processList.value = res;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadProcessList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard>
|
||||
<VTable
|
||||
:headers="headers"
|
||||
:items="processList"
|
||||
item-key="fullName"
|
||||
class="table-rounded"
|
||||
hide-default-footer
|
||||
disable-sort
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="header in headers" :key="header" :id="header">
|
||||
{{ header }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<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`" />
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</VCard>
|
||||
</template>
|
||||
74
src/views/dashboard/AnalyticsStorage.vue
Normal file
74
src/views/dashboard/AnalyticsStorage.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import { formatFileSize } from "@/@core/utils/formatters";
|
||||
import api from "@/api";
|
||||
import triangleDark from "@images/misc/triangle-dark.png";
|
||||
import triangleLight from "@images/misc/triangle-light.png";
|
||||
import trophy from "@images/misc/trophy.png";
|
||||
import { useTheme } from "vuetify";
|
||||
|
||||
const { global } = useTheme();
|
||||
const triangleBg = computed(() =>
|
||||
global.name.value === "light" ? triangleLight : triangleDark
|
||||
);
|
||||
|
||||
// 总存储空间
|
||||
const storage = ref(0);
|
||||
// 已使用存储空间
|
||||
const used = ref(0);
|
||||
|
||||
// 计算已使用存储空间百分比,精确到小数点后1位
|
||||
const usedPercent = computed(() => {
|
||||
return Math.round((used.value / storage.value) * 1000) / 10;
|
||||
});
|
||||
|
||||
// 调用API,查询存储空间
|
||||
const getStorage = async () => {
|
||||
try {
|
||||
const res: Storage = await api.get("dashboard/storage");
|
||||
storage.value = res.total_storage;
|
||||
used.value = res.used_storage;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getStorage();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard title="存储空间" subtitle="" class="position-relative">
|
||||
<VCardText>
|
||||
<h5 class="text-2xl font-weight-medium text-primary">
|
||||
{{ formatFileSize(storage) }}
|
||||
</h5>
|
||||
<p class="mt-2">已使用 {{ usedPercent }}% 🚀</p>
|
||||
<p class="mt-1"><VProgressLinear :model-value="usedPercent" /></p>
|
||||
</VCardText>
|
||||
|
||||
<!-- Triangle Background -->
|
||||
<VImg :src="triangleBg" class="triangle-bg flip-in-rtl" />
|
||||
|
||||
<!-- Trophy -->
|
||||
<VImg :src="trophy" class="trophy" />
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@layouts/styles/mixins" as layoutsMixins;
|
||||
|
||||
.v-card .triangle-bg {
|
||||
position: absolute;
|
||||
inline-size: 10.375rem;
|
||||
inset-block-end: 0;
|
||||
inset-inline-end: 0;
|
||||
}
|
||||
|
||||
.v-card .trophy {
|
||||
position: absolute;
|
||||
inline-size: 4.9375rem;
|
||||
inset-block-end: 2rem;
|
||||
inset-inline-end: 2rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,81 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const statistics = [
|
||||
{
|
||||
title: 'Sales',
|
||||
stats: '245k',
|
||||
icon: 'mdi-trending-up',
|
||||
color: 'primary',
|
||||
},
|
||||
{
|
||||
title: 'Customers',
|
||||
stats: '12.5k',
|
||||
icon: 'mdi-account-outline',
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
title: 'Product',
|
||||
stats: '1.54k',
|
||||
icon: 'mdi-cellphone-link',
|
||||
color: 'warning',
|
||||
},
|
||||
{
|
||||
title: 'Revenue',
|
||||
stats: '$88k',
|
||||
icon: 'mdi-currency-usd',
|
||||
color: 'info',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>Transactions</VCardTitle>
|
||||
|
||||
<template #append>
|
||||
<div class="me-n3">
|
||||
<MoreBtn />
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<h6 class="text-sm font-weight-medium mb-12">
|
||||
<span>Total 48.5% Growth 😎</span>
|
||||
<span class="font-weight-regular"> this month</span>
|
||||
</h6>
|
||||
|
||||
<VRow>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column">
|
||||
<span class="text-caption">
|
||||
{{ item.title }}
|
||||
</span>
|
||||
<span class="text-h6">{{ item.stats }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
@@ -1,204 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
interface DataItem {
|
||||
responsiveId: string
|
||||
id: number
|
||||
fullName: string
|
||||
post: string
|
||||
email: string
|
||||
city: string
|
||||
start_date: string
|
||||
salary: number
|
||||
age: string
|
||||
experience: string
|
||||
status: number
|
||||
}
|
||||
|
||||
const data: DataItem[] = [
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 95,
|
||||
fullName: 'Edwina Ebsworth',
|
||||
post: 'Human Resources Assistant',
|
||||
email: 'eebsworth2m@sbwire.com',
|
||||
city: 'Puzi',
|
||||
start_date: '09/27/2018',
|
||||
salary: 19586.23,
|
||||
age: '27',
|
||||
experience: '2 Years',
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 1,
|
||||
fullName: 'Korrie O\'Crevy',
|
||||
post: 'Nuclear Power Engineer',
|
||||
email: 'kocrevy0@thetimes.co.uk',
|
||||
city: 'Krasnosilka',
|
||||
start_date: '09/23/2016',
|
||||
salary: 23896.35,
|
||||
age: '61',
|
||||
experience: '1 Year',
|
||||
status: 2,
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 7,
|
||||
fullName: 'Eileen Diehn',
|
||||
post: 'Environmental Specialist',
|
||||
email: 'ediehn6@163.com',
|
||||
city: 'Lampuyang',
|
||||
start_date: '10/15/2017',
|
||||
salary: 18991.67,
|
||||
age: '59',
|
||||
experience: '9 Years',
|
||||
status: 3,
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 11,
|
||||
fullName: 'De Falloon',
|
||||
post: 'Sales Representative',
|
||||
email: 'dfalloona@ifeng.com',
|
||||
city: 'Colima',
|
||||
start_date: '06/12/2018',
|
||||
salary: 19252.12,
|
||||
age: '30',
|
||||
experience: '0 Year',
|
||||
status: 4,
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 3,
|
||||
fullName: 'Stella Ganderton',
|
||||
post: 'Operator',
|
||||
email: 'sganderton2@tuttocitta.it',
|
||||
city: 'Golcowa',
|
||||
start_date: '03/24/2018',
|
||||
salary: 13076.28,
|
||||
age: '66',
|
||||
experience: '6 Years',
|
||||
status: 5,
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 5,
|
||||
fullName: 'Harmonia Nisius',
|
||||
post: 'Senior Cost Accountant',
|
||||
email: 'hnisius4@gnu.org',
|
||||
city: 'Lucan',
|
||||
start_date: '08/25/2017',
|
||||
salary: 10909.52,
|
||||
age: '33',
|
||||
experience: '3 Years',
|
||||
status: 2,
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 6,
|
||||
fullName: 'Genevra Honeywood',
|
||||
post: 'Geologist',
|
||||
email: 'ghoneywood5@narod.ru',
|
||||
city: 'Maofan',
|
||||
start_date: '06/01/2017',
|
||||
salary: 17803.8,
|
||||
age: '61',
|
||||
experience: '1 Year',
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 4,
|
||||
fullName: 'Dorolice Crossman',
|
||||
post: 'Cost Accountant',
|
||||
email: 'dcrossman3@google.co.jp',
|
||||
city: 'Paquera',
|
||||
start_date: '12/03/2017',
|
||||
salary: 12336.17,
|
||||
age: '22',
|
||||
experience: '2 Years',
|
||||
status: 2,
|
||||
},
|
||||
]
|
||||
|
||||
const status: Record<DataItem['status'], string> = {
|
||||
1: 'Current',
|
||||
2: 'Professional',
|
||||
3: 'Rejected',
|
||||
4: 'Resigned',
|
||||
5: 'Applied',
|
||||
}
|
||||
|
||||
const statusColor: Record<typeof status[number], string> = {
|
||||
Current: 'primary',
|
||||
Professional: 'success',
|
||||
Rejected: 'error',
|
||||
Resigned: 'warning',
|
||||
Applied: 'info',
|
||||
}
|
||||
|
||||
const headers = [
|
||||
'NAME',
|
||||
'EMAIL',
|
||||
'DATE',
|
||||
'SALARY',
|
||||
'AGE',
|
||||
'STATUS',
|
||||
]
|
||||
|
||||
const usreList = data
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard>
|
||||
<VTable
|
||||
:headers="headers"
|
||||
:items="usreList"
|
||||
item-key="fullName"
|
||||
class="table-rounded"
|
||||
hide-default-footer
|
||||
disable-sort
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
v-for="header in headers"
|
||||
:key="header"
|
||||
>
|
||||
{{ header }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="row in data"
|
||||
:key="row.fullName"
|
||||
>
|
||||
<!-- name -->
|
||||
|
||||
<td>
|
||||
<div class="d-flex flex-column">
|
||||
<h6 class="text-sm font-weight-medium">{{ row.fullName }}</h6>
|
||||
<span class="text-xs">{{ row.post }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="text-sm" v-text="row.email" />
|
||||
<td class="text-sm" v-text="row.start_date" />
|
||||
<td class="text-sm" v-text="`$${row.salary}`" />
|
||||
<td class="text-sm" v-text="row.age" />
|
||||
<!-- status -->
|
||||
<td>
|
||||
<VChip
|
||||
size="small"
|
||||
:color="statusColor[status[row.status]]"
|
||||
class="text-capitalize"
|
||||
>
|
||||
{{ status[row.status] }}
|
||||
</VChip>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</VCard>
|
||||
</template>
|
||||
Reference in New Issue
Block a user