chore
This commit is contained in:
54
src/components/station-card.vue
Normal file
54
src/components/station-card.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NCard, NStatistic, NTag, NIcon, NGrid, NGi } from 'naive-ui';
|
||||||
|
import { Wifi } from '@vicons/ionicons5';
|
||||||
|
import { toRefs } from 'vue';
|
||||||
|
|
||||||
|
// 定义组件props
|
||||||
|
interface Props {
|
||||||
|
name: string;
|
||||||
|
online: boolean;
|
||||||
|
offlineDeviceCount: number;
|
||||||
|
alarmCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const { name, online, offlineDeviceCount, alarmCount } = toRefs(props);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NCard bordered hoverable size="small" :title="name" class="station-card">
|
||||||
|
<template #header-extra>
|
||||||
|
<NTag :type="online ? 'success' : 'error'" size="small">
|
||||||
|
<template #icon>
|
||||||
|
<NIcon><Wifi /></NIcon>
|
||||||
|
</template>
|
||||||
|
{{ online ? '在线' : '离线' }}
|
||||||
|
</NTag>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<NGrid :cols="2">
|
||||||
|
<NGi>
|
||||||
|
<NStatistic label="离线设备" :value="offlineDeviceCount" :tabular-nums="true">
|
||||||
|
<template #suffix>
|
||||||
|
<span class="stat-suffix">台</span>
|
||||||
|
</template>
|
||||||
|
</NStatistic>
|
||||||
|
</NGi>
|
||||||
|
<NGi>
|
||||||
|
<NStatistic label="告警记录" :value="alarmCount" :tabular-nums="true">
|
||||||
|
<template #suffix>
|
||||||
|
<span class="stat-suffix">条</span>
|
||||||
|
</template>
|
||||||
|
</NStatistic>
|
||||||
|
</NGi>
|
||||||
|
</NGrid>
|
||||||
|
</template>
|
||||||
|
</NCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.stat-suffix {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
33
src/composables/query/use-station-list-query.ts
Normal file
33
src/composables/query/use-station-list-query.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type { Station } from '@/apis/domains';
|
||||||
|
import { ndmVerify } from '@/apis/requests';
|
||||||
|
import { useQuery } from '@tanstack/vue-query';
|
||||||
|
import { useStationStore } from '@/stores/station';
|
||||||
|
import axios from 'axios';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
|
export function useStationListQuery() {
|
||||||
|
const stationStore = useStationStore();
|
||||||
|
const { stationList } = storeToRefs(stationStore);
|
||||||
|
useQuery({
|
||||||
|
queryKey: ['station-list'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data: ndmStationList } = await axios.get<{ code: string; name: string }[]>(`/minio/ndm/ndm-stations.json?_t=${dayjs().unix()}`);
|
||||||
|
|
||||||
|
stationList.value = ndmStationList.map<Station>((record) => ({
|
||||||
|
code: record.code ?? '',
|
||||||
|
name: record.name ?? '',
|
||||||
|
online: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const pingResultList = await Promise.allSettled(stationList.value.map((station) => ndmVerify(station.code)));
|
||||||
|
|
||||||
|
stationList.value = stationList.value.map((station, index) => ({
|
||||||
|
...station,
|
||||||
|
online: pingResultList[index].status === 'fulfilled',
|
||||||
|
}));
|
||||||
|
|
||||||
|
return stationList.value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
7
src/pages/alarm-page.vue
Normal file
7
src/pages/alarm-page.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>alarm</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
31
src/pages/dashboard-page.vue
Normal file
31
src/pages/dashboard-page.vue
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import StationCard from '@/components/station-card.vue';
|
||||||
|
import { NGrid, NGi } from 'naive-ui';
|
||||||
|
import { useQuery } from '@tanstack/vue-query';
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
const stations = ref(
|
||||||
|
Array.from({ length: 32 }).map((_, i) => ({
|
||||||
|
name: `车站 ${i + 1}`,
|
||||||
|
online: Math.random() > 0.5,
|
||||||
|
offlineDeviceCount: Math.floor(Math.random() * 20),
|
||||||
|
alarmCount: Math.floor(Math.random() * 10),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
// const query = useQuery({
|
||||||
|
// queryKey: ['line-devices'],
|
||||||
|
// queryFn: async () => {},
|
||||||
|
// });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NGrid :cols="8" :x-gap="12" :y-gap="12">
|
||||||
|
<NGi v-for="station in stations" :key="station.name">
|
||||||
|
<StationCard :name="station.name" :online="station.online" :offline-device-count="station.offlineDeviceCount" :alarm-count="station.alarmCount" />
|
||||||
|
</NGi>
|
||||||
|
</NGrid>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
7
src/pages/device-page.vue
Normal file
7
src/pages/device-page.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>device</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
7
src/pages/log-page.vue
Normal file
7
src/pages/log-page.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>log</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
101
src/pages/login-page.vue
Normal file
101
src/pages/login-page.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
import { NLayout, NCard, NForm, NFormItem, NInput, NButton, NFlex } from 'naive-ui';
|
||||||
|
import ThemeSwitch from '@/components/theme-switch.vue';
|
||||||
|
import { useUserStore } from '@/stores/user';
|
||||||
|
import type { LoginParams } from '@/apis/models/user';
|
||||||
|
import { randomNum } from '@/utils/random-num';
|
||||||
|
import { userClient } from '@/apis/client';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
|
|
||||||
|
const formData = reactive<LoginParams>({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
code: '',
|
||||||
|
key: randomNum(24, 16),
|
||||||
|
grantType: 'PASSWORD',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate: login, isPending: loading } = useMutation({
|
||||||
|
mutationFn: async (params: LoginParams) => {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
await userStore.userLogin(params);
|
||||||
|
const [err] = await userClient.post<void>(`/api/ndm/ndmKeepAlive/verify`, {}, { timeout: 5000 });
|
||||||
|
if (err) throw err;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
window.$message.success('登录成功');
|
||||||
|
router.push('/');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NLayout position="absolute" class="login-page">
|
||||||
|
<div class="login-card-container">
|
||||||
|
<NCard class="login-card">
|
||||||
|
<ThemeSwitch class="theme-switch" />
|
||||||
|
<NFlex vertical justify="center" align="center">
|
||||||
|
<span class="platform-title">网络设备管理平台</span>
|
||||||
|
<span class="login-title">登录</span>
|
||||||
|
<NForm :model="formData" label-placement="top" class="login-form">
|
||||||
|
<NFormItem label="用户名" path="username">
|
||||||
|
<NInput v-model:value="formData.username" placeholder="请输入用户名" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem label="密码" path="password">
|
||||||
|
<NInput v-model:value="formData.password" type="password" show-password-on="click" placeholder="请输入密码" />
|
||||||
|
</NFormItem>
|
||||||
|
</NForm>
|
||||||
|
<NButton type="primary" block strong :loading="loading" @click="() => login(formData)"> 登录 </NButton>
|
||||||
|
</NFlex>
|
||||||
|
</NCard>
|
||||||
|
</div>
|
||||||
|
</NLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.login-page {
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
width: 400px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 32px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-switch {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 16px;
|
||||||
|
right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-title {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
20
src/pages/not-found-page.vue
Normal file
20
src/pages/not-found-page.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NResult, NButton } from 'naive-ui';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const goHome = () => {
|
||||||
|
router.push('/');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NResult status="404" title="404 页面不存在" style="margin-top: 50px">
|
||||||
|
<template #footer>
|
||||||
|
<NButton @click="goHome">返回</NButton>
|
||||||
|
</template>
|
||||||
|
</NResult>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
7
src/pages/statistics-page.vue
Normal file
7
src/pages/statistics-page.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>statistics</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
Reference in New Issue
Block a user