Files
dashboard/frontend/src/views/home/index.vue
T

188 lines
5.1 KiB
Vue
Raw Normal View History

2026-07-17 09:35:32 +08:00
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import type { RouteKey } from '@elegant-router/types';
import { fetchGetDashboardToday } from '@/service/api';
import { useRouterPush } from '@/hooks/common/router';
import { useAuthStore } from '@/store/modules/auth';
defineOptions({ name: 'Home' });
interface TodayModel {
id: number;
brand_name?: string;
name?: string;
create_at?: string | null;
}
interface TodayOta {
id: number;
verName?: string;
model?: string;
create_at?: string | null;
}
interface QuickLink {
key: RouteKey;
title: string;
desc: string;
icon: string;
}
const authStore = useAuthStore();
const { routerPushByKey } = useRouterPush();
const loading = ref(false);
const todayModels = ref<TodayModel[]>([]);
const todayOtas = ref<TodayOta[]>([]);
const modelCount = computed(() => todayModels.value.length);
const otaCount = computed(() => todayOtas.value.length);
const quickLinks: QuickLink[] = [
{
key: 'headphone_brand',
title: '耳机品牌管理',
desc: '维护耳机品牌信息',
2026-07-20 15:45:52 +08:00
icon: 'mdi:tag-multiple-outline'
2026-07-17 09:35:32 +08:00
},
{
key: 'headphone_model',
title: '耳机型号管理',
desc: '维护耳机型号与配置',
2026-07-20 15:45:52 +08:00
icon: 'mdi:earbuds'
2026-07-17 09:35:32 +08:00
},
{
key: 'upgrade_ota',
title: 'OTA 管理',
desc: '固件升级包管理',
2026-07-20 15:45:52 +08:00
icon: 'mdi:cellphone-arrow-down'
2026-07-17 09:35:32 +08:00
},
{
key: 'share-code_log',
title: '分享日志',
desc: '查看分享码使用记录',
2026-07-20 15:45:52 +08:00
icon: 'mdi:file-document-outline'
2026-07-17 09:35:32 +08:00
},
{
key: 'toolbox_luxsin-controller',
title: 'Luxsin 控制器',
desc: '局域网设备数据同步',
2026-07-20 15:45:52 +08:00
icon: 'mdi:tune-vertical'
2026-07-17 09:35:32 +08:00
}
];
2026-06-18 17:37:08 +08:00
2026-07-17 09:35:32 +08:00
function formatTime(isoStr?: string | null) {
if (!isoStr) return '';
const d = new Date(isoStr);
const pad = (n: number) => String(n).padStart(2, '0');
return `${pad(d.getHours())}:${pad(d.getMinutes())}`;
2026-06-30 10:41:03 +08:00
}
async function loadTodayStats() {
2026-07-17 09:35:32 +08:00
loading.value = true;
const { data, error } = await fetchGetDashboardToday();
if (!error && data) {
todayModels.value = (data.models as TodayModel[]) || [];
todayOtas.value = (data.otas as TodayOta[]) || [];
2026-06-30 10:41:03 +08:00
}
2026-07-17 09:35:32 +08:00
loading.value = false;
2026-06-30 10:41:03 +08:00
}
2026-07-17 09:35:32 +08:00
function go(key: RouteKey) {
routerPushByKey(key);
2026-06-18 17:37:08 +08:00
}
2026-06-30 10:41:03 +08:00
onMounted(() => {
2026-07-17 09:35:32 +08:00
loadTodayStats();
});
2026-06-18 17:37:08 +08:00
</script>
2026-07-17 09:35:32 +08:00
<template>
<NSpace vertical :size="16">
<NCard :bordered="false" class="card-wrapper">
<NThing>
<template #header>欢迎回来{{ authStore.userInfo.userName || '用户' }}</template>
<template #description>Luxsin CMS · 从下方快捷入口或左侧菜单开始操作</template>
</NThing>
</NCard>
<NGrid cols="1 s:2 m:3" responsive="screen" :x-gap="16" :y-gap="16">
<NGi v-for="item in quickLinks" :key="item.key">
<NCard
:bordered="false"
class="card-wrapper quick-link-card cursor-pointer"
hoverable
@click="go(item.key)"
>
<div class="flex-y-center gap-12px">
<div class="quick-link-icon">
<SvgIcon :icon="item.icon" class="text-24px" />
</div>
<div>
<div class="text-16px font-medium">{{ item.title }}</div>
<NText depth="3" class="text-13px">{{ item.desc }}</NText>
</div>
</div>
</NCard>
</NGi>
</NGrid>
<NSpin :show="loading">
<NGrid cols="1 s:2" responsive="screen" :x-gap="16" :y-gap="16">
<NGi>
<NCard :bordered="false" class="card-wrapper" title="今日新增型号">
<template #header-extra>
<NTag type="info" size="small">{{ modelCount }}</NTag>
</template>
<NEmpty v-if="!todayModels.length" description="暂无新增" />
<NList v-else>
<NListItem v-for="item in todayModels" :key="item.id">
<div class="flex-y-center justify-between">
<span>{{ item.brand_name }} {{ item.name }}</span>
<NText depth="3">{{ formatTime(item.create_at) }}</NText>
</div>
</NListItem>
</NList>
</NCard>
</NGi>
<NGi>
<NCard :bordered="false" class="card-wrapper" title="今日新增 OTA">
<template #header-extra>
<NTag type="success" size="small">{{ otaCount }}</NTag>
</template>
<NEmpty v-if="!todayOtas.length" description="暂无新增" />
<NList v-else>
<NListItem v-for="item in todayOtas" :key="item.id">
<div class="flex-y-center justify-between">
<span>{{ item.verName }} · {{ item.model }}</span>
<NText depth="3">{{ formatTime(item.create_at) }}</NText>
</div>
</NListItem>
</NList>
</NCard>
</NGi>
</NGrid>
</NSpin>
</NSpace>
</template>
2026-06-30 10:41:03 +08:00
2026-07-17 09:35:32 +08:00
<style scoped>
.quick-link-card {
transition: transform 0.2s ease;
2026-06-30 10:41:03 +08:00
}
2026-07-17 09:35:32 +08:00
.quick-link-card:hover {
transform: translateY(-2px);
2026-06-30 10:41:03 +08:00
}
2026-07-17 09:35:32 +08:00
.quick-link-icon {
width: 44px;
height: 44px;
border-radius: 12px;
2026-06-30 10:41:03 +08:00
display: flex;
align-items: center;
justify-content: center;
2026-07-17 09:35:32 +08:00
background: rgba(125, 211, 252, 0.12);
color: #38bdf8;
2026-06-18 17:37:08 +08:00
}
</style>