|
|
@@ -2,7 +2,14 @@
|
|
|
import { computed, ref } from 'vue'
|
|
|
import { useRoute } from 'vue-router'
|
|
|
import { IotDeviceApi } from '@/api/pms/device'
|
|
|
-import { Odometer, CircleCheckFilled, CircleCloseFilled, DataLine } from '@element-plus/icons-vue'
|
|
|
+import {
|
|
|
+ Odometer,
|
|
|
+ CircleCheckFilled,
|
|
|
+ CircleCloseFilled,
|
|
|
+ DataLine,
|
|
|
+ Crop,
|
|
|
+ FullScreen
|
|
|
+} from '@element-plus/icons-vue'
|
|
|
import { AnimatedCountTo } from '@/components/AnimatedCountTo'
|
|
|
import { neonColors } from '@/utils/td-color'
|
|
|
import dayjs from 'dayjs'
|
|
|
@@ -10,6 +17,7 @@ import * as echarts from 'echarts'
|
|
|
import { cancelAllRequests, IotStatApi } from '@/api/pms/stat'
|
|
|
import { Dimensions, formatIotValue, HeaderItem, useSocketBus } from '@/utils/useSocketBus'
|
|
|
import { rangeShortcuts } from '@/utils/formatTime'
|
|
|
+import { useFullscreen } from '@vueuse/core'
|
|
|
|
|
|
const { query } = useRoute()
|
|
|
|
|
|
@@ -409,8 +417,8 @@ function render() {
|
|
|
}
|
|
|
},
|
|
|
dataZoom: [
|
|
|
- { type: 'inside', xAxisIndex: 0 },
|
|
|
- { type: 'slider', xAxisIndex: 0 }
|
|
|
+ { type: 'inside', xAxisIndex: 0, start: 75, end: 100 },
|
|
|
+ { type: 'slider', xAxisIndex: 0, start: 75, end: 100 }
|
|
|
],
|
|
|
yAxis: {
|
|
|
type: 'value',
|
|
|
@@ -702,14 +710,18 @@ onUnmounted(() => {
|
|
|
if (chart) chart.resize()
|
|
|
})
|
|
|
})
|
|
|
+
|
|
|
+const targetArea = ref(null)
|
|
|
+
|
|
|
+const { toggle, isFullscreen } = useFullscreen(targetArea)
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
<div
|
|
|
- class="grid grid-cols-[260px_1fr] grid-rows-[80px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
|
|
|
+ class="grid grid-rows-[80px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
|
|
|
>
|
|
|
<div
|
|
|
- class="grid-col-span-2 rounded-xl shadow-sm border border-gray-100 border-solid px-6 flex items-center justify-between shrink-0 bg-gradient-to-r from-blue-100 to-white"
|
|
|
+ class="rounded-xl shadow-sm border border-gray-100 border-solid px-6 flex items-center justify-between shrink-0 bg-gradient-to-r from-blue-100 to-white"
|
|
|
>
|
|
|
<div class="flex items-center gap-4">
|
|
|
<div
|
|
|
@@ -747,151 +759,165 @@ onUnmounted(() => {
|
|
|
</template>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <div ref="targetArea" class="h-full min-h-0 relative">
|
|
|
+ <div class="grid grid-cols-[260px_1fr] gap-4 h-full">
|
|
|
+ <el-scrollbar
|
|
|
+ class="rounded-xl shadow-sm border border-gray-100 border-solid overflow-hidden bg-gradient-to-b from-blue-100 to-white"
|
|
|
+ view-class="flex flex-col min-h-full"
|
|
|
+ v-loading="dimensionLoading"
|
|
|
+ >
|
|
|
+ <template v-for="citem in dimensionsContent" :key="citem.label">
|
|
|
+ <template v-if="citem.judgment ? Boolean(citem.value.length) : true">
|
|
|
+ <div
|
|
|
+ class="sticky-title bg-blue-100 z-10 flex justify-between items-center py-3 px-4 border-0 border-solid"
|
|
|
+ >
|
|
|
+ <span class="font-bold text-sm text-gray-700! flex items-center gap-2">
|
|
|
+ <el-icon><component :is="citem.icon" /></el-icon>
|
|
|
+ {{ citem.label }}
|
|
|
+ </span>
|
|
|
+ <span
|
|
|
+ class="text-xs px-2 py-0.5 rounded-full font-mono"
|
|
|
+ :class="[citem.countBg, citem.countColor]"
|
|
|
+ >
|
|
|
+ {{ citem.value.length }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
|
|
|
- <el-scrollbar
|
|
|
- class="rounded-xl shadow-sm border border-gray-100 border-solid overflow-hidden bg-gradient-to-b from-blue-100 to-white"
|
|
|
- view-class="flex flex-col min-h-full"
|
|
|
- v-loading="dimensionLoading"
|
|
|
- >
|
|
|
- <template v-for="citem in dimensionsContent" :key="citem.label">
|
|
|
- <template v-if="citem.judgment ? Boolean(citem.value.length) : true">
|
|
|
- <div
|
|
|
- class="sticky-title bg-blue-100 z-88 flex justify-between items-center py-3 px-4 border-0 border-solid"
|
|
|
- >
|
|
|
- <span class="font-bold text-sm text-gray-700! flex items-center gap-2">
|
|
|
- <el-icon><component :is="citem.icon" /></el-icon>
|
|
|
- {{ citem.label }}
|
|
|
- </span>
|
|
|
- <span
|
|
|
- class="text-xs px-2 py-0.5 rounded-full font-mono"
|
|
|
- :class="[citem.countBg, citem.countColor]"
|
|
|
- >
|
|
|
- {{ citem.value.length }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
+ <div class="px-3 pb-4 pt-2 space-y-3">
|
|
|
+ <div
|
|
|
+ :data-disabled="disabledDimensions.includes(item.identifier)"
|
|
|
+ v-for="item in citem.value"
|
|
|
+ :key="item.identifier"
|
|
|
+ @click="handleClickSpec(item.name)"
|
|
|
+ class="dimension-card group relative p-3 rounded-lg border border-solid bg-transparent border-gray-300 transition-all duration-300 cursor-pointer select-none data-[disabled=true]:pointer-events-none"
|
|
|
+ :class="{ 'is-active': selectedDimension[item.name] }"
|
|
|
+ :style="{
|
|
|
+ '--theme-color': item.color,
|
|
|
+ '--theme-bg-hover': item.bgHover,
|
|
|
+ '--theme-bg-active': item.bgActive
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <div class="flex justify-between items-center mb-1">
|
|
|
+ <span
|
|
|
+ class="text-xs font-medium text-gray-500 transition-colors truncate pr-2 group-hover:text-[var(--theme-color)]"
|
|
|
+ :class="{ 'text-[var(--theme-color)]!': selectedDimension[item.name] }"
|
|
|
+ >
|
|
|
+ {{ item.name }}
|
|
|
+ </span>
|
|
|
+ <div
|
|
|
+ class="size-2 rounded-full transition-all duration-300 shadow-sm"
|
|
|
+ :class="selectedDimension[item.name] ? 'scale-100' : 'scale-0'"
|
|
|
+ :style="{ backgroundColor: item.color, boxShadow: `0 0 6px ${item.color}` }"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex items-baseline justify-between relative z-9">
|
|
|
+ <animated-count-to
|
|
|
+ v-if="!item.isText"
|
|
|
+ :value="Number(item.value)"
|
|
|
+ :duration="500"
|
|
|
+ :suffix="item.suffix"
|
|
|
+ class="text-lg font-bold font-mono tracking-tight text-slate-800"
|
|
|
+ />
|
|
|
+ <span v-else class="text-lg font-bold font-mono tracking-tight text-slate-800">
|
|
|
+ {{ item.value }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="absolute left-0 top-3 bottom-3 w-1 rounded-r transition-all duration-300"
|
|
|
+ :class="
|
|
|
+ selectedDimension[item.name]
|
|
|
+ ? 'opacity-100 shadow-[0_0_8px_currentColor]'
|
|
|
+ : 'opacity-0'
|
|
|
+ "
|
|
|
+ :style="{ backgroundColor: item.color, color: item.color }"
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </el-scrollbar>
|
|
|
|
|
|
- <div class="px-3 pb-4 pt-2 space-y-3">
|
|
|
- <div
|
|
|
- :data-disabled="disabledDimensions.includes(item.identifier)"
|
|
|
- v-for="item in citem.value"
|
|
|
- :key="item.identifier"
|
|
|
- @click="handleClickSpec(item.name)"
|
|
|
- class="dimension-card group relative p-3 rounded-lg border border-solid bg-transparent border-gray-300 transition-all duration-300 cursor-pointer select-none data-[disabled=true]:pointer-events-none"
|
|
|
- :class="{ 'is-active': selectedDimension[item.name] }"
|
|
|
- :style="{
|
|
|
- '--theme-color': item.color,
|
|
|
- '--theme-bg-hover': item.bgHover,
|
|
|
- '--theme-bg-active': item.bgActive
|
|
|
- }"
|
|
|
- >
|
|
|
- <div class="flex justify-between items-center mb-1">
|
|
|
+ <div
|
|
|
+ class="rounded-xl shadow-sm border border-gray-100 border-solid p-4 flex flex-col bg-gradient-to-b from-blue-100 to-white"
|
|
|
+ >
|
|
|
+ <header class="flex items-center justify-between mb-4">
|
|
|
+ <h3 class="flex items-center gap-2">
|
|
|
+ <div
|
|
|
+ class="i-material-symbols:area-chart-outline-rounded text-sky size-6"
|
|
|
+ text-sky
|
|
|
+ ></div>
|
|
|
+ 数据趋势
|
|
|
+ </h3>
|
|
|
+ <div class="flex gap-4">
|
|
|
+ <el-button type="primary" size="default" @click="exportChart">导出为图片</el-button>
|
|
|
+ <el-button size="default" @click="reset">重置</el-button>
|
|
|
+ <el-date-picker
|
|
|
+ v-model="selectedDate"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
+ type="datetimerange"
|
|
|
+ unlink-panels
|
|
|
+ start-placeholder="开始日期"
|
|
|
+ end-placeholder="结束日期"
|
|
|
+ :shortcuts="rangeShortcuts"
|
|
|
+ size="default"
|
|
|
+ class="w-100!"
|
|
|
+ placement="bottom-end"
|
|
|
+ @change="handleDateChange"
|
|
|
+ />
|
|
|
+ <el-button
|
|
|
+ size="default"
|
|
|
+ :type="isFullscreen ? 'info' : 'primary'"
|
|
|
+ :icon="isFullscreen ? Crop : FullScreen"
|
|
|
+ @click="toggle"
|
|
|
+ >
|
|
|
+ {{ isFullscreen ? '退出全屏' : '全屏' }}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </header>
|
|
|
+
|
|
|
+ <div class="flex flex-1">
|
|
|
+ <div class="flex gap-1 select-none">
|
|
|
+ <div
|
|
|
+ v-for="item of maxmin"
|
|
|
+ :key="item.name"
|
|
|
+ :style="{
|
|
|
+ '--theme-bg-hover': item.bgHover
|
|
|
+ }"
|
|
|
+ class="w-8 h-full flex flex-col items-center justify-between py-2 gap-1 rounded-full group relative bg-transparent border border-solid border-transparent transition-all duration-300 hover:bg-[var(--theme-bg-hover)] hover-border-gray-200 hover:shadow-md cursor-pointer active:scale-95"
|
|
|
+ @click="handleClickSpec(item.name)"
|
|
|
+ >
|
|
|
+ <span class="[writing-mode:sideways-lr] text-xs text-gray-400">{{ item.max }}</span>
|
|
|
+ <div
|
|
|
+ class="flex-1 w-0.5 rounded-full opacity-40 group-hover:opacity-100 transition-opacity duration-300"
|
|
|
+ :style="{ backgroundColor: item.color }"
|
|
|
+ ></div>
|
|
|
<span
|
|
|
- class="text-xs font-medium text-gray-500 transition-colors truncate pr-2 group-hover:text-[var(--theme-color)]"
|
|
|
- :class="{ 'text-[var(--theme-color)]!': selectedDimension[item.name] }"
|
|
|
+ class="[writing-mode:sideways-lr] text-sm font-bold tracking-widest"
|
|
|
+ :style="{ color: item.color }"
|
|
|
>
|
|
|
{{ item.name }}
|
|
|
</span>
|
|
|
<div
|
|
|
- class="size-2 rounded-full transition-all duration-300 shadow-sm"
|
|
|
- :class="selectedDimension[item.name] ? 'scale-100' : 'scale-0'"
|
|
|
- :style="{ backgroundColor: item.color, boxShadow: `0 0 6px ${item.color}` }"
|
|
|
+ class="flex-1 w-0.5 rounded-full opacity-40 group-hover:opacity-100 transition-opacity duration-300"
|
|
|
+ :style="{ backgroundColor: item.color }"
|
|
|
></div>
|
|
|
+ <span class="[writing-mode:sideways-lr] text-xs text-gray-400">{{ item.min }}</span>
|
|
|
</div>
|
|
|
-
|
|
|
- <div class="flex items-baseline justify-between relative z-10">
|
|
|
- <animated-count-to
|
|
|
- v-if="!item.isText"
|
|
|
- :value="Number(item.value)"
|
|
|
- :duration="500"
|
|
|
- :suffix="item.suffix"
|
|
|
- class="text-lg font-bold font-mono tracking-tight text-slate-800"
|
|
|
- />
|
|
|
- <span v-else class="text-lg font-bold font-mono tracking-tight text-slate-800">
|
|
|
- {{ item.value }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="flex flex-1 min-w-0 bg-gray-50/30 rounded-lg border border-dashed border-gray-200 ml-2 relative overflow-hidden bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:20px_20px]"
|
|
|
+ >
|
|
|
<div
|
|
|
- class="absolute left-0 top-3 bottom-3 w-1 rounded-r transition-all duration-300"
|
|
|
- :class="
|
|
|
- selectedDimension[item.name]
|
|
|
- ? 'opacity-100 shadow-[0_0_8px_currentColor]'
|
|
|
- : 'opacity-0'
|
|
|
- "
|
|
|
- :style="{ backgroundColor: item.color, color: item.color }"
|
|
|
+ v-loading="chartLoading"
|
|
|
+ element-loading-background="transparent"
|
|
|
+ ref="chartRef"
|
|
|
+ class="w-full h-full"
|
|
|
>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </template>
|
|
|
- </template>
|
|
|
- </el-scrollbar>
|
|
|
-
|
|
|
- <div
|
|
|
- class="rounded-xl shadow-sm border border-gray-100 border-solid p-4 flex flex-col bg-gradient-to-b from-blue-100 to-white"
|
|
|
- >
|
|
|
- <header class="flex items-center justify-between mb-4">
|
|
|
- <h3 class="flex items-center gap-2">
|
|
|
- <div class="i-material-symbols:area-chart-outline-rounded text-sky size-6" text-sky></div>
|
|
|
- 数据趋势
|
|
|
- </h3>
|
|
|
- <div class="flex gap-4">
|
|
|
- <el-button type="primary" size="default" @click="exportChart">导出为图片</el-button>
|
|
|
- <el-button size="default" @click="reset">重置</el-button>
|
|
|
- <el-date-picker
|
|
|
- v-model="selectedDate"
|
|
|
- value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
- type="datetimerange"
|
|
|
- unlink-panels
|
|
|
- start-placeholder="开始日期"
|
|
|
- end-placeholder="结束日期"
|
|
|
- :shortcuts="rangeShortcuts"
|
|
|
- size="default"
|
|
|
- class="w-100!"
|
|
|
- placement="bottom-end"
|
|
|
- @change="handleDateChange"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </header>
|
|
|
-
|
|
|
- <div class="flex flex-1">
|
|
|
- <div class="flex gap-1 select-none">
|
|
|
- <div
|
|
|
- v-for="item of maxmin"
|
|
|
- :key="item.name"
|
|
|
- :style="{
|
|
|
- '--theme-bg-hover': item.bgHover
|
|
|
- }"
|
|
|
- class="w-8 h-full flex flex-col items-center justify-between py-2 gap-1 rounded-full group relative bg-transparent border border-solid border-transparent transition-all duration-300 hover:bg-[var(--theme-bg-hover)] hover-border-gray-200 hover:shadow-md cursor-pointer active:scale-95"
|
|
|
- @click="handleClickSpec(item.name)"
|
|
|
- >
|
|
|
- <span class="[writing-mode:sideways-lr] text-xs text-gray-400">{{ item.max }}</span>
|
|
|
- <div
|
|
|
- class="flex-1 w-0.5 rounded-full opacity-40 group-hover:opacity-100 transition-opacity duration-300"
|
|
|
- :style="{ backgroundColor: item.color }"
|
|
|
- ></div>
|
|
|
- <span
|
|
|
- class="[writing-mode:sideways-lr] text-sm font-bold tracking-widest"
|
|
|
- :style="{ color: item.color }"
|
|
|
- >
|
|
|
- {{ item.name }}
|
|
|
- </span>
|
|
|
- <div
|
|
|
- class="flex-1 w-0.5 rounded-full opacity-40 group-hover:opacity-100 transition-opacity duration-300"
|
|
|
- :style="{ backgroundColor: item.color }"
|
|
|
- ></div>
|
|
|
- <span class="[writing-mode:sideways-lr] text-xs text-gray-400">{{ item.min }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="flex flex-1 min-w-0 bg-gray-50/30 rounded-lg border border-dashed border-gray-200 ml-2 relative overflow-hidden"
|
|
|
- >
|
|
|
- <div
|
|
|
- v-loading="chartLoading"
|
|
|
- element-loading-background="transparent"
|
|
|
- ref="chartRef"
|
|
|
- class="w-full h-full"
|
|
|
- >
|
|
|
- </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -938,4 +964,14 @@ onUnmounted(() => {
|
|
|
background-color: #94a3b8;
|
|
|
opacity: 1;
|
|
|
}
|
|
|
+
|
|
|
+:fullscreen {
|
|
|
+ padding: 16px;
|
|
|
+ background-color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+/* 兼容写法 */
|
|
|
+::backdrop {
|
|
|
+ background-color: #fff;
|
|
|
+}
|
|
|
</style>
|