add dashboard

This commit is contained in:
jxxghp
2023-07-10 16:42:31 +08:00
parent f8f6e79d18
commit fb19e43421
9 changed files with 315 additions and 411 deletions

View File

@@ -67,3 +67,26 @@ export const formatFileSize = (bytes: number) => {
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
// 将时间秒格式化为时分秒
export const formatSeconds = (seconds: number) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
let formattedTime = '';
if (hours > 0) {
formattedTime += `${hours}小时`;
}
if (minutes > 0) {
formattedTime += `${minutes}`;
}
if (remainingSeconds > 0 || formattedTime === '') {
formattedTime += `${remainingSeconds}`;
}
return formattedTime;
}

View File

@@ -426,3 +426,43 @@ export interface User {
is_superuser: boolean,
avatar: string,
};
// 存储空间
export interface Storage {
total_storage: number,
used_storage: number,
}
// 媒体统计
export interface MediaStatistic {
// 电影总数
movie_count: number,
// 电视剧总数
tv_count: number,
// 电视剧总集数
episode_count: number,
// 用户数量
user_count: number,
}
// 后台进程
export interface Process {
// 进程ID
pid: number,
// 进程名称
name: string,
// 进程状态
status: string,
// 进程启动时间
create_time: number,
// 进程运行时间
run_time: number,
// 进程CPU占用率
cpu: number,
// 进程内存占用
memory: number,
}

View File

@@ -1,115 +1,82 @@
<script setup lang="ts">
import AnalyticsAward from '@/views/dashboard/AnalyticsAward.vue'
import AnalyticsBarCharts from '@/views/dashboard/AnalyticsBarCharts.vue'
import AnalyticsDepositWithdraw from '@/views/dashboard/AnalyticsDepositWithdraw.vue'
import AnalyticsSalesByCountries from '@/views/dashboard/AnalyticsSalesByCountries.vue'
import AnalyticsTotalEarning from '@/views/dashboard/AnalyticsTotalEarning.vue'
import AnalyticsTotalProfitLineCharts from '@/views/dashboard/AnalyticsTotalProfitLineCharts.vue'
import AnalyticsTransactions from '@/views/dashboard/AnalyticsTransactions.vue'
import AnalyticsUserTable from '@/views/dashboard/AnalyticsUserTable.vue'
import AnalyticsWeeklyOverview from '@/views/dashboard/AnalyticsWeeklyOverview.vue'
import CardStatisticsVertical from '@/components/cards/CardStatisticsVertical.vue'
import CardStatisticsVertical from "@/components/cards/CardStatisticsVertical.vue";
import AnalyticsBarCharts from "@/views/dashboard/AnalyticsBarCharts.vue";
import AnalyticsDepositWithdraw from "@/views/dashboard/AnalyticsDepositWithdraw.vue";
import AnalyticsMediaStatistic from "@/views/dashboard/AnalyticsMediaStatistic.vue";
import AnalyticsProcesses from "@/views/dashboard/AnalyticsProcesses.vue";
import AnalyticsSalesByCountries from "@/views/dashboard/AnalyticsSalesByCountries.vue";
import AnalyticsStorage from "@/views/dashboard/AnalyticsStorage.vue";
import AnalyticsTotalEarning from "@/views/dashboard/AnalyticsTotalEarning.vue";
import AnalyticsTotalProfitLineCharts from "@/views/dashboard/AnalyticsTotalProfitLineCharts.vue";
import AnalyticsWeeklyOverview from "@/views/dashboard/AnalyticsWeeklyOverview.vue";
const totalProfit = {
title: 'Total Profit',
color: 'secondary',
icon: 'mdi-poll',
stats: '$25.6k',
title: "Total Profit",
color: "secondary",
icon: "mdi-poll",
stats: "$25.6k",
change: 42,
subtitle: 'Weekly Project',
}
subtitle: "Weekly Project",
};
const newProject = {
title: 'New Project',
color: 'primary',
icon: 'mdi-briefcase-variant-outline',
stats: '862',
title: "New Project",
color: "primary",
icon: "mdi-briefcase-variant-outline",
stats: "862",
change: -18,
subtitle: 'Yearly Project',
}
subtitle: "Yearly Project",
};
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="4"
>
<AnalyticsAward />
<VCol cols="12" md="4">
<AnalyticsStorage />
</VCol>
<VCol
cols="12"
md="8"
>
<AnalyticsTransactions />
<VCol cols="12" md="8">
<AnalyticsMediaStatistic />
</VCol>
<VCol
cols="12"
md="4"
>
<VCol cols="12" md="4">
<AnalyticsWeeklyOverview />
</VCol>
<VCol
cols="12"
md="4"
>
<VCol cols="12" md="4">
<AnalyticsTotalEarning />
</VCol>
<VCol
cols="12"
md="4"
>
<VCol cols="12" md="4">
<VRow class="match-height">
<VCol
cols="12"
sm="6"
>
<VCol cols="12" sm="6">
<AnalyticsTotalProfitLineCharts />
</VCol>
<VCol
cols="12"
sm="6"
>
<VCol cols="12" sm="6">
<CardStatisticsVertical v-bind="totalProfit" />
</VCol>
<VCol
cols="12"
sm="6"
>
<VCol cols="12" sm="6">
<CardStatisticsVertical v-bind="newProject" />
</VCol>
<VCol
cols="12"
sm="6"
>
<VCol cols="12" sm="6">
<AnalyticsBarCharts />
</VCol>
</VRow>
</VCol>
<VCol
cols="12"
md="4"
>
<VCol cols="12" md="4">
<AnalyticsSalesByCountries />
</VCol>
<VCol
cols="12"
md="8"
>
<VCol cols="12" md="8">
<AnalyticsDepositWithdraw />
</VCol>
<VCol cols="12">
<AnalyticsUserTable />
<AnalyticsProcesses />
</VCol>
</VRow>
</template>

View File

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

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

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

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

View File

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

View File

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