|
@@ -1,172 +1,100 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="page">
|
|
|
|
|
- <div class="gva-card-box">
|
|
|
|
|
- <div class="gva-card gva-top-card">
|
|
|
|
|
- <div class="gva-top-card-left">
|
|
|
|
|
- <div class="gva-top-card-left-title">早安,管理员,请开始一天的工作吧</div>
|
|
|
|
|
- <div class="gva-top-card-left-dot">{{ weatherInfo }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <img
|
|
|
|
|
- src="@/assets/dashboard.png"
|
|
|
|
|
- class="gva-top-card-right"
|
|
|
|
|
- alt
|
|
|
|
|
- >
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div class="monitor-page">
|
|
|
|
|
+ <div class="monitor-header">
|
|
|
|
|
+ <h2>智慧停车监控</h2>
|
|
|
|
|
+ <span class="time">{{ now }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
- <div class="gva-card-box">
|
|
|
|
|
- <div class="gva-card">
|
|
|
|
|
- <div class="gva-card-title">数据统计</div>
|
|
|
|
|
- <div class="p-4">
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col
|
|
|
|
|
- :xs="24"
|
|
|
|
|
- :sm="18"
|
|
|
|
|
- >
|
|
|
|
|
- <echarts-line />
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col
|
|
|
|
|
- :xs="24"
|
|
|
|
|
- :sm="6"
|
|
|
|
|
- >
|
|
|
|
|
- <dashboard-table />
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <el-row :gutter="16" class="stat-row">
|
|
|
|
|
+ <el-col :span="6">
|
|
|
|
|
+ <el-card shadow="hover">
|
|
|
|
|
+ <el-statistic title="车位余量">
|
|
|
|
|
+ <template #default>{{ data.used_spaces }} / {{ data.total_spaces }}</template>
|
|
|
|
|
+ <template #suffix>
|
|
|
|
|
+ <span :style="{ color: data.total_spaces > 0 && data.used_spaces >= data.total_spaces ? '#f56c6c' : '#67c23a' }">车位</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-statistic>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="6">
|
|
|
|
|
+ <el-card shadow="hover">
|
|
|
|
|
+ <el-statistic title="在场车辆" :value="data.in_park">
|
|
|
|
|
+ <template #suffix>台</template>
|
|
|
|
|
+ </el-statistic>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="6">
|
|
|
|
|
+ <el-card shadow="hover">
|
|
|
|
|
+ <el-statistic title="今日收入" :value="data.today_income" prefix="¥" :precision="2" />
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="6">
|
|
|
|
|
+ <el-card shadow="hover">
|
|
|
|
|
+ <el-statistic title="进 / 出" :value="data.today_entry">
|
|
|
|
|
+ <template #suffix>/ {{ data.today_exit }}</template>
|
|
|
|
|
+ </el-statistic>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+
|
|
|
|
|
+ <el-card class="device-card">
|
|
|
|
|
+ <template #header>设备状态</template>
|
|
|
|
|
+ <div class="device-list">
|
|
|
|
|
+ <div v-for="d in data.devices" :key="d.channel_name" class="device-item">
|
|
|
|
|
+ <el-tag :type="d.device_status === 'online' ? 'success' : 'danger'" size="small">
|
|
|
|
|
+ {{ d.device_status === 'online' ? '在线' : '离线' }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ <span class="device-label">{{ d.channel_name }}</span>
|
|
|
|
|
+ <span class="device-name">{{ d.device_name }} ({{ d.direction === 'in' ? '入口' : '出口' }})</span>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <el-empty v-if="!data.devices || data.devices.length === 0" description="暂无设备" :image-size="60" />
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
|
+ </el-card>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-import EchartsLine from '@/view/dashboard/dashboardCharts/echartsLine.vue'
|
|
|
|
|
-import DashboardTable from '@/view/dashboard/dashboardTable/dashboardTable.vue'
|
|
|
|
|
-import { ref } from 'vue'
|
|
|
|
|
-import { useRouter } from 'vue-router'
|
|
|
|
|
-import { useWeatherInfo } from '@/view/dashboard/weather.js'
|
|
|
|
|
-import SelectImage from '@/components/selectImage/selectImage.vue'
|
|
|
|
|
|
|
+import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
|
|
|
|
+import { getMonitor } from '@/api/dashboard'
|
|
|
|
|
|
|
|
-defineOptions({
|
|
|
|
|
- name: 'Dashboard'
|
|
|
|
|
|
|
+const data = reactive({
|
|
|
|
|
+ total_spaces: 0, used_spaces: 0, in_park: 0,
|
|
|
|
|
+ today_income: 0, today_entry: 0, today_exit: 0,
|
|
|
|
|
+ devices: []
|
|
|
})
|
|
})
|
|
|
|
|
+const now = ref('')
|
|
|
|
|
+let timer = null
|
|
|
|
|
|
|
|
-const weatherInfo = useWeatherInfo()
|
|
|
|
|
-
|
|
|
|
|
-const toolCards = ref([
|
|
|
|
|
- {
|
|
|
|
|
- label: '用户管理',
|
|
|
|
|
- icon: 'monitor',
|
|
|
|
|
- name: 'user',
|
|
|
|
|
- color: '#ff9c6e',
|
|
|
|
|
- bg: 'rgba(255, 156, 110,.3)'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- label: '角色管理',
|
|
|
|
|
- icon: 'setting',
|
|
|
|
|
- name: 'authority',
|
|
|
|
|
- color: '#69c0ff',
|
|
|
|
|
- bg: 'rgba(105, 192, 255,.3)'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- label: '菜单管理',
|
|
|
|
|
- icon: 'menu',
|
|
|
|
|
- name: 'menu',
|
|
|
|
|
- color: '#b37feb',
|
|
|
|
|
- bg: 'rgba(179, 127, 235,.3)'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- label: '代码生成器',
|
|
|
|
|
- icon: 'cpu',
|
|
|
|
|
- name: 'autoCode',
|
|
|
|
|
- color: '#ffd666',
|
|
|
|
|
- bg: 'rgba(255, 214, 102,.3)'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- label: '表单生成器',
|
|
|
|
|
- icon: 'document-checked',
|
|
|
|
|
- name: 'formCreate',
|
|
|
|
|
- color: '#ff85c0',
|
|
|
|
|
- bg: 'rgba(255, 133, 192,.3)'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- label: '关于我们',
|
|
|
|
|
- icon: 'user',
|
|
|
|
|
- name: 'about',
|
|
|
|
|
- color: '#5cdbd3',
|
|
|
|
|
- bg: 'rgba(92, 219, 211,.3)'
|
|
|
|
|
- }
|
|
|
|
|
-])
|
|
|
|
|
-
|
|
|
|
|
-const router = useRouter()
|
|
|
|
|
|
|
+function updateTime() {
|
|
|
|
|
+ now.value = new Date().toLocaleString('zh-CN')
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-const toTarget = (name) => {
|
|
|
|
|
- router.push({ name })
|
|
|
|
|
|
|
+async function fetchData() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getMonitor()
|
|
|
|
|
+ if (res.code === 0) Object.assign(data, res.data)
|
|
|
|
|
+ } catch (e) { /* keep last data */ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-</script>
|
|
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ updateTime()
|
|
|
|
|
+ fetchData()
|
|
|
|
|
+ timer = setInterval(() => { updateTime(); fetchData() }, 10000)
|
|
|
|
|
+})
|
|
|
|
|
|
|
|
-<style lang="scss" scoped>
|
|
|
|
|
-.page {
|
|
|
|
|
- @apply p-0;
|
|
|
|
|
- .gva-card-box{
|
|
|
|
|
- @apply p-4;
|
|
|
|
|
- &+.gva-card-box{
|
|
|
|
|
- @apply pt-0;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- .gva-card {
|
|
|
|
|
- @apply box-border bg-white rounded h-auto px-6 py-8 overflow-hidden shadow-sm;
|
|
|
|
|
- .gva-card-title{
|
|
|
|
|
- @apply pb-5 border-t-0 border-l-0 border-r-0 border-b border-solid border-gray-100;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- .gva-top-card {
|
|
|
|
|
- @apply h-72 flex items-center justify-between text-gray-500;
|
|
|
|
|
- &-left {
|
|
|
|
|
- @apply h-full flex flex-col w-auto;
|
|
|
|
|
- &-title {
|
|
|
|
|
- @apply text-3xl text-gray-600;
|
|
|
|
|
- }
|
|
|
|
|
- &-dot {
|
|
|
|
|
- @apply mt-4 text-gray-600 text-lg;
|
|
|
|
|
- }
|
|
|
|
|
- &-item{
|
|
|
|
|
- +.gva-top-card-left-item{
|
|
|
|
|
- margin-top: 24px;
|
|
|
|
|
- }
|
|
|
|
|
- margin-top: 14px;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- &-right {
|
|
|
|
|
- height: 600px;
|
|
|
|
|
- width: 600px;
|
|
|
|
|
- margin-top: 28px;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- ::v-deep(.el-card__header){
|
|
|
|
|
- @apply p-0 border-gray-200;
|
|
|
|
|
- }
|
|
|
|
|
- .card-header{
|
|
|
|
|
- @apply pb-5 border-b border-solid border-gray-200 border-t-0 border-l-0 border-r-0;
|
|
|
|
|
- }
|
|
|
|
|
- .quick-entrance-items {
|
|
|
|
|
- @apply flex items-center justify-center text-center text-gray-800;
|
|
|
|
|
- .quick-entrance-item {
|
|
|
|
|
- @apply px-8 py-6 flex items-center flex-col transition-all duration-100 ease-in-out rounded-lg cursor-pointer;
|
|
|
|
|
- &:hover{
|
|
|
|
|
- @apply shadow-lg;
|
|
|
|
|
- }
|
|
|
|
|
- &-icon {
|
|
|
|
|
- @apply flex items-center h-16 w-16 rounded-lg justify-center mx-0 my-auto text-2xl;
|
|
|
|
|
- }
|
|
|
|
|
- p {
|
|
|
|
|
- @apply mt-2.5;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-.dashboard-icon {
|
|
|
|
|
- @apply flex items-center text-xl mr-2 text-blue-400;
|
|
|
|
|
-}
|
|
|
|
|
|
|
+onUnmounted(() => clearInterval(timer))
|
|
|
|
|
+</script>
|
|
|
|
|
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.monitor-page { padding: 16px; }
|
|
|
|
|
+.monitor-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
|
|
|
|
+.monitor-header h2 { margin: 0; font-size: 22px; }
|
|
|
|
|
+.time { color: #909399; font-size: 14px; }
|
|
|
|
|
+.stat-row { margin-bottom: 16px; }
|
|
|
|
|
+.stat-row .el-card { text-align: center; }
|
|
|
|
|
+.device-card { margin-top: 0; }
|
|
|
|
|
+.device-list { display: flex; flex-wrap: wrap; gap: 12px; }
|
|
|
|
|
+.device-item { display: flex; align-items: center; gap: 8px; padding: 8px 14px; background: #f5f7fa; border-radius: 6px; }
|
|
|
|
|
+.device-label { font-weight: 500; }
|
|
|
|
|
+.device-name { color: #909399; font-size: 13px; }
|
|
|
</style>
|
|
</style>
|