|
|
@@ -1,1370 +1,86 @@
|
|
|
-<template>
|
|
|
- <!-- 第一行:统计卡片行 -->
|
|
|
- <div class="page-container">
|
|
|
- <el-row :gutter="16" class="summary">
|
|
|
- <!-- 原有的统计卡片部分保持不变 -->
|
|
|
- <el-col :sm="3" :xs="12">
|
|
|
- <SummaryCard
|
|
|
- :value="device.total || 0"
|
|
|
- icon="fa-solid:project-diagram"
|
|
|
- icon-bg-color="text-blue-500"
|
|
|
- icon-color="bg-blue-100"
|
|
|
- :title="t('stat.deviceCount')"
|
|
|
- />
|
|
|
- </el-col>
|
|
|
- <el-col :sm="3" :xs="12">
|
|
|
- <SummaryCard
|
|
|
- :value="maintain.total || 0"
|
|
|
- icon="fa-solid:list"
|
|
|
- icon-bg-color="text-pink-500"
|
|
|
- icon-color="bg-blue-100"
|
|
|
- :title="t('stat.maintenanceOrder')"
|
|
|
- />
|
|
|
- </el-col>
|
|
|
- <el-col :sm="3" :xs="12">
|
|
|
- <SummaryCard
|
|
|
- :value="fill.unfilledCount || 0"
|
|
|
- icon="fa-solid:times-circle"
|
|
|
- icon-bg-color="text-purple-500"
|
|
|
- icon-color="bg-purple-100"
|
|
|
- :title="t('stat.operationNotFilled')"
|
|
|
- />
|
|
|
- </el-col>
|
|
|
- <el-col :sm="3" :xs="12">
|
|
|
- <SummaryCard
|
|
|
- :value="fill.filledCount || 0"
|
|
|
- icon="fa-solid:award"
|
|
|
- icon-bg-color="text-purple-500"
|
|
|
- icon-color="bg-purple-100"
|
|
|
- :title="t('stat.operationFilled')"
|
|
|
- />
|
|
|
- </el-col>
|
|
|
- <el-col :sm="3" :xs="12">
|
|
|
- <SummaryCard
|
|
|
- :value="by.todo || 0"
|
|
|
- icon="fa-solid:times-circle"
|
|
|
- icon-bg-color="text-green-500"
|
|
|
- icon-color="bg-green-100"
|
|
|
- :title="t('stat.notMaintained')"
|
|
|
- />
|
|
|
- </el-col>
|
|
|
- <el-col :sm="3" :xs="12">
|
|
|
- <SummaryCard
|
|
|
- :value="by.finished || 0"
|
|
|
- icon="fa-solid:award"
|
|
|
- icon-bg-color="text-green-500"
|
|
|
- icon-color="bg-green-100"
|
|
|
- :title="t('stat.maintained')"
|
|
|
- />
|
|
|
- </el-col>
|
|
|
- <el-col :sm="3" :xs="12">
|
|
|
- <SummaryCard
|
|
|
- :value="inspectt.todo || 0"
|
|
|
- icon="fa-solid:times-circle"
|
|
|
- icon-bg-color="text-yellow-500"
|
|
|
- icon-color="bg-yellow-100"
|
|
|
- :title="t('stat.notInspected')"
|
|
|
- />
|
|
|
- </el-col>
|
|
|
- <el-col :sm="3" :xs="12">
|
|
|
- <SummaryCard
|
|
|
- :value="inspectt.finished || 0"
|
|
|
- icon="fa-solid:award"
|
|
|
- icon-bg-color="text-yellow-500"
|
|
|
- icon-color="bg-yellow-100"
|
|
|
- :title="t('stat.inspected')"
|
|
|
- />
|
|
|
- </el-col>
|
|
|
- <!-- 其他统计卡片... -->
|
|
|
- </el-row>
|
|
|
- <!-- <el-row :gutter="16" class="mb-4">-->
|
|
|
- <!-- <el-col :span="6">-->
|
|
|
- <!-- <el-card class="stat-card" shadow="never">-->
|
|
|
- <!-- <div class="flex flex-col">-->
|
|
|
- <!-- <div class="flex justify-between items-center mb-1">-->
|
|
|
- <!-- <span class="text-gray-500 text-base font-medium">设备数量</span>-->
|
|
|
- <!-- <Icon icon="ep:menu" class="text-[32px] text-blue-400" />-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- <span class="text-3xl font-bold text-gray-700">-->
|
|
|
- <!-- {{ device.total }}-->
|
|
|
- <!-- </span>-->
|
|
|
- <!-- <el-divider class="my-2" />-->
|
|
|
- <!-- <div class="flex justify-between items-center text-gray-400 text-sm">-->
|
|
|
- <!-- <span>今日新增</span>-->
|
|
|
- <!-- <span class="text-green-500">+{{ device.today }}</span>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- </el-card>-->
|
|
|
- <!-- </el-col>-->
|
|
|
- <!-- <el-col :span="6">-->
|
|
|
- <!-- <el-card class="stat-card" shadow="never">-->
|
|
|
- <!-- <div class="flex flex-col">-->
|
|
|
- <!-- <div class="flex justify-between items-center mb-1">-->
|
|
|
- <!-- <span class="text-gray-500 text-base font-medium">维修工单数量</span>-->
|
|
|
- <!-- <Icon icon="ep:box" class="text-[32px] text-orange-400" />-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- <span class="text-3xl font-bold text-gray-700">{{ maintain.total }}</span>-->
|
|
|
- <!-- <el-divider class="my-2" />-->
|
|
|
- <!-- <div class="flex justify-between items-center text-gray-400 text-sm">-->
|
|
|
- <!-- <span>今日新增</span>-->
|
|
|
- <!-- <span class="text-green-500">+{{ maintain.today }}</span>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- </el-card>-->
|
|
|
- <!-- </el-col>-->
|
|
|
- <!-- <el-col :span="6">-->
|
|
|
- <!-- <el-card class="stat-card" shadow="never">-->
|
|
|
- <!-- <div class="flex flex-col">-->
|
|
|
- <!-- <div class="flex justify-between items-center mb-1">-->
|
|
|
- <!-- <span class="text-gray-500 text-base font-medium">保养工单数量</span>-->
|
|
|
- <!-- <Icon icon="ep:cpu" class="text-[32px] text-purple-400" />-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- <span class="text-3xl font-bold text-gray-700">{{ work.total }}</span>-->
|
|
|
- <!-- <el-divider class="my-2" />-->
|
|
|
- <!-- <div class="flex justify-between items-center text-gray-400 text-sm">-->
|
|
|
- <!-- <span>今日新增</span>-->
|
|
|
- <!-- <span class="text-green-500">+{{ work.today }}</span>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- </el-card>-->
|
|
|
- <!-- </el-col>-->
|
|
|
- <!-- <el-col :span="6">-->
|
|
|
- <!-- <el-card class="stat-card" shadow="never">-->
|
|
|
- <!-- <div class="flex flex-col">-->
|
|
|
- <!-- <div class="flex justify-between items-center mb-1">-->
|
|
|
- <!-- <span class="text-gray-500 text-base font-medium">巡检工单数量</span>-->
|
|
|
- <!-- <Icon icon="ep:camera" class="text-[32px] text-teal-400" />-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- <span class="text-3xl font-bold text-gray-700">-->
|
|
|
- <!-- {{ inspect.total }}-->
|
|
|
- <!-- </span>-->
|
|
|
- <!-- <el-divider class="my-2" />-->
|
|
|
- <!-- <div class="flex justify-between items-center text-gray-400 text-sm">-->
|
|
|
- <!-- <span>今日新增</span>-->
|
|
|
- <!-- <span class="text-green-500">+{{ inspect.today }}</span>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- </el-card>-->
|
|
|
- <!-- </el-col>-->
|
|
|
- <!-- </el-row>-->
|
|
|
-
|
|
|
- <!-- 第二行:图表行 -->
|
|
|
- <el-row :gutter="16" class="mb-4">
|
|
|
- <el-col :span="6">
|
|
|
- <el-card class="chart-card" shadow="never">
|
|
|
- <template #header>
|
|
|
- <div class="flex items-center justify-between">
|
|
|
- <span class="text-base font-medium" style="color: #b6c8da">{{ t('stat.mttr') }}</span>
|
|
|
- <span class="text-base font-medium" style="color: #b6c8da">{{
|
|
|
- t('stat.materialsUnderInventory')
|
|
|
- }}</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <div class="flex flex-col h-[250px]">
|
|
|
- <!-- <div class="flex justify-between items-center text-gray-400">-->
|
|
|
- <!-- <span>MTTR</span>-->
|
|
|
-
|
|
|
- <!-- </div>-->
|
|
|
- <!-- <el-divider />-->
|
|
|
- <div class="flex justify-between items-center mb-1 mt-15">
|
|
|
- <!-- <span class="text-gray-500 text-base font-medium">平均解决时间</span>-->
|
|
|
- <!-- <Icon icon="ep:menu" class="text-[32px] text-blue-400" />-->
|
|
|
- <span class="text-5xl font-bold text-gray-700" style="color: lightseagreen">
|
|
|
- {{ mttr + 'h' }}
|
|
|
- </span>
|
|
|
- <span class="text-5xl font-bold text-gray-700" style="color: indianred">
|
|
|
- {{ safe }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </el-card>
|
|
|
- </el-col>
|
|
|
- <el-col :span="9">
|
|
|
- <el-card class="chart-card" shadow="never">
|
|
|
- <template #header>
|
|
|
- <div class="flex items-center">
|
|
|
- <span class="text-base font-medium" style="color: #b6c8da">{{
|
|
|
- t('stat.deviceStatus')
|
|
|
- }}</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <div ref="statusChartRef" class="h-[250px]"></div>
|
|
|
- </el-card>
|
|
|
- </el-col>
|
|
|
- <el-col :span="9">
|
|
|
- <el-card class="chart-card" shadow="never">
|
|
|
- <template #header>
|
|
|
- <div class="flex items-center">
|
|
|
- <span class="text-base font-medium" style="color: #b6c8da">{{
|
|
|
- t('stat.deviceClassifyTop5')
|
|
|
- }}</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <div ref="topContainer" class="h-[250px]"></div>
|
|
|
- </el-card>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <!-- 第三行:消息统计行 -->
|
|
|
- <el-row :gutter="16" class="mb-4">
|
|
|
- <el-col :span="8">
|
|
|
- <el-card class="chart-card" shadow="never">
|
|
|
- <template #header>
|
|
|
- <div class="flex items-center justify-between">
|
|
|
- <span class="text-base font-medium" style="color: #b6c8da">日报完成率</span>
|
|
|
- <div>
|
|
|
- <el-date-picker
|
|
|
- v-model="completeRateParams.createTime"
|
|
|
- value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
- type="daterange"
|
|
|
- start-placeholder="开始日期"
|
|
|
- end-placeholder="结束日期"
|
|
|
- :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
|
|
- class="!w-200px"
|
|
|
- @change="handleDateChange"
|
|
|
- size="small"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <!-- <div ref="activeDom" class="h-[320px]"></div> -->
|
|
|
- <Echart :options="myoption" :height="320" />
|
|
|
- </el-card>
|
|
|
- </el-col>
|
|
|
- <el-col :span="16">
|
|
|
- <el-card class="chart-card" shadow="never">
|
|
|
- <template #header>
|
|
|
- <div class="flex items-center justify-between">
|
|
|
- <span class="text-base font-medium" style="color: #b6c8da">{{
|
|
|
- t('stat.orderCount')
|
|
|
- }}</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <div ref="qxRef" class="h-[320px]"></div>
|
|
|
- </el-card>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script setup lang="ts" name="Index">
|
|
|
-import * as echarts from 'echarts/core'
|
|
|
-import { BarChart, GaugeChart, LineChart, PieChart } from 'echarts/charts' // 显式导入柱状图模块
|
|
|
-import {
|
|
|
- GridComponent,
|
|
|
- LegendComponent,
|
|
|
- TitleComponent,
|
|
|
- ToolboxComponent,
|
|
|
- TooltipComponent
|
|
|
-} from 'echarts/components'
|
|
|
-import { LabelLayout, UniversalTransition } from 'echarts/features'
|
|
|
-import { CanvasRenderer } from 'echarts/renderers'
|
|
|
-import { useElementSize } from '@vueuse/core'
|
|
|
-import {
|
|
|
- IotStatisticsDeviceMessageSummaryRespVO,
|
|
|
- IotStatisticsSummaryRespVO
|
|
|
-} from '@/api/iot/statistics'
|
|
|
+<script lang="ts" setup>
|
|
|
+import { DESIGN_HEIGHT, DESIGN_WIDTH } from '@/utils/kb'
|
|
|
+import hsummary from './kb/hsummary.vue'
|
|
|
+import hmttr from './kb/hmttr.vue'
|
|
|
+import hdeviceType from './kb/hdeviceType.vue'
|
|
|
+import dayfinish from './kb/dayfinish.vue'
|
|
|
+import hsafe from './kb/hsafe.vue'
|
|
|
+import hdeviceStatus from './kb/hdeviceStatus.vue'
|
|
|
+import horderTrend from './kb/horderTrend.vue'
|
|
|
|
|
|
-import { IotStatApi } from '@/api/pms/stat'
|
|
|
-import SummaryCard from '@/components/SummaryCard/index.vue'
|
|
|
-import { reactive, ref } from 'vue'
|
|
|
-import { useLocaleStore } from '@/store/modules/locale'
|
|
|
-import Echart from '@/components/Echart/src/Echart.vue'
|
|
|
+const company = ref('首页')
|
|
|
|
|
|
-// TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
|
|
|
+const wrapperRef = ref<HTMLDivElement>()
|
|
|
+const scale = ref(1)
|
|
|
|
|
|
-/** IoT 首页 */
|
|
|
-defineOptions({ name: 'IoTHome' })
|
|
|
+let resizeObserver: ResizeObserver | null = null
|
|
|
+let resizeRaf = 0
|
|
|
|
|
|
-// TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
|
|
|
-echarts.use([
|
|
|
- TooltipComponent,
|
|
|
- LegendComponent,
|
|
|
- PieChart,
|
|
|
- CanvasRenderer,
|
|
|
- LabelLayout,
|
|
|
- TitleComponent,
|
|
|
- ToolboxComponent,
|
|
|
- GridComponent,
|
|
|
- LineChart,
|
|
|
- UniversalTransition,
|
|
|
- GaugeChart,
|
|
|
- BarChart
|
|
|
-])
|
|
|
+const targetWrapperStyle = computed(() => ({
|
|
|
+ width: `${DESIGN_WIDTH * scale.value}px`,
|
|
|
+ height: `${DESIGN_HEIGHT * scale.value}px`
|
|
|
+}))
|
|
|
|
|
|
-const myoption = ref<any>({
|
|
|
- xAxis: {
|
|
|
- type: 'category',
|
|
|
- data: ['四川瑞都', '瑞恒兴域', '瑞鹰国际'],
|
|
|
- // 文字颜色
|
|
|
- axisLabel: {
|
|
|
- color: '#fff'
|
|
|
- }
|
|
|
- },
|
|
|
+const targetAreaStyle = computed(() => ({
|
|
|
+ width: `${DESIGN_WIDTH}px`,
|
|
|
+ height: `${DESIGN_HEIGHT}px`,
|
|
|
+ transform: `scale(${scale.value})`,
|
|
|
+ transformOrigin: '0 0'
|
|
|
+}))
|
|
|
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis',
|
|
|
- axisPointer: {
|
|
|
- label: {
|
|
|
- backgroundColor: '#6a7985'
|
|
|
- }
|
|
|
- },
|
|
|
- formatter: function (params: any) {
|
|
|
- const p = params && params[0] ? params[0] : params
|
|
|
- return `${p.name}: ${p.data}%`
|
|
|
- }
|
|
|
- },
|
|
|
+function updateScale() {
|
|
|
+ cancelAnimationFrame(resizeRaf)
|
|
|
|
|
|
- legend: {
|
|
|
- top: '5%',
|
|
|
- right: '10%',
|
|
|
- align: 'left',
|
|
|
- orient: 'vertical',
|
|
|
- icon: 'circle',
|
|
|
- textStyle: {
|
|
|
- color: 'white'
|
|
|
- }
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- type: 'value',
|
|
|
- axisLabel: {
|
|
|
- color: '#fff',
|
|
|
- formatter: '{value}%'
|
|
|
- }
|
|
|
- },
|
|
|
- series: [
|
|
|
- {
|
|
|
- data: [],
|
|
|
- type: 'bar'
|
|
|
- }
|
|
|
- ]
|
|
|
-})
|
|
|
-const completeRateParams = reactive({
|
|
|
- createTime: []
|
|
|
-})
|
|
|
+ resizeRaf = requestAnimationFrame(() => {
|
|
|
+ const wrapper = wrapperRef.value
|
|
|
+ if (!wrapper) return
|
|
|
|
|
|
-// 计算近一周时间
|
|
|
-const end = new Date()
|
|
|
-const start = new Date()
|
|
|
+ const { clientWidth, clientHeight } = wrapper
|
|
|
+ if (!clientWidth || !clientHeight) return
|
|
|
|
|
|
-const now = new Date()
|
|
|
-// 构造:今年 1 月 1 日 00:00:00
|
|
|
-const firstDay = new Date(now.getFullYear(), 0, 1, 0, 0, 0, 0)
|
|
|
-// 转时间戳(毫秒),如需秒级时间戳,加 .getTime() / 1000
|
|
|
-
|
|
|
-start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000) // 近一周
|
|
|
-
|
|
|
-// 格式化日期为后端需要的格式
|
|
|
-const formatDate = (date): string => {
|
|
|
- const year = date.getFullYear()
|
|
|
- const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
- const day = String(date.getDate()).padStart(2, '0')
|
|
|
- const hours = String(date.getHours()).padStart(2, '0')
|
|
|
- const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
|
- const seconds = String(date.getSeconds()).padStart(2, '0')
|
|
|
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
|
-}
|
|
|
-const initMyoption = async () => {
|
|
|
- // 只在初始化时设置默认值,不覆盖已有值
|
|
|
- if (!completeRateParams.createTime || completeRateParams.createTime.length === 0) {
|
|
|
- const start = new Date()
|
|
|
- start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000) // 近一周
|
|
|
-
|
|
|
- const end = new Date()
|
|
|
-
|
|
|
- // 将开始时间设置为当天的 00:00:00
|
|
|
- const startTime = new Date(start)
|
|
|
- startTime.setHours(0, 0, 0, 0)
|
|
|
-
|
|
|
- // 将结束时间设置为当天的 23:59:59
|
|
|
- const endTime = new Date(end)
|
|
|
- endTime.setHours(23, 59, 59, 999)
|
|
|
-
|
|
|
- // 格式化为 YYYY-MM-DD HH:mm:ss
|
|
|
- const formatDate = (date) => {
|
|
|
- const year = date.getFullYear()
|
|
|
- const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
- const day = String(date.getDate()).padStart(2, '0')
|
|
|
- const hours = String(date.getHours()).padStart(2, '0')
|
|
|
- const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
|
- const seconds = String(date.getSeconds()).padStart(2, '0')
|
|
|
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
|
- }
|
|
|
-
|
|
|
- completeRateParams.createTime = [formatDate(startTime), formatDate(endTime)]
|
|
|
- }
|
|
|
-
|
|
|
- const res = await IotStatApi.getCompleteRate(completeRateParams)
|
|
|
-
|
|
|
- const list = []
|
|
|
- res.forEach((item) => {
|
|
|
- // 后端返回 rate 为小数(如 0.85),这里转换为整数百分比(如 85)以便图表显示
|
|
|
- if (item.department === '瑞恒兴域') {
|
|
|
- list.push(Math.trunc(Number(item['瑞恒兴域']) * 100))
|
|
|
- } else {
|
|
|
- list.push(Math.trunc(Number(item.rate) * 100))
|
|
|
- }
|
|
|
+ scale.value = Math.min(clientWidth / DESIGN_WIDTH, clientHeight / DESIGN_HEIGHT)
|
|
|
})
|
|
|
-
|
|
|
- myoption.value.series[0].data = list
|
|
|
-}
|
|
|
-
|
|
|
-const handleDateChange = (val) => {
|
|
|
- if (!val || val.length !== 2) return
|
|
|
-
|
|
|
- const [start, end] = val
|
|
|
-
|
|
|
- // 将开始时间设置为当天的 00:00:00
|
|
|
- const startTime = new Date(start)
|
|
|
- startTime.setHours(0, 0, 0, 0)
|
|
|
-
|
|
|
- // 将结束时间设置为当天的 23:59:59
|
|
|
- const endTime = new Date(end)
|
|
|
- endTime.setHours(23, 59, 59, 999)
|
|
|
-
|
|
|
- // 格式化为 YYYY-MM-DD HH:mm:ss
|
|
|
- const formatDate = (date) => {
|
|
|
- const year = date.getFullYear()
|
|
|
- const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
- const day = String(date.getDate()).padStart(2, '0')
|
|
|
- const hours = String(date.getHours()).padStart(2, '0')
|
|
|
- const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
|
- const seconds = String(date.getSeconds()).padStart(2, '0')
|
|
|
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
|
- }
|
|
|
-
|
|
|
- completeRateParams.createTime = [formatDate(startTime), formatDate(endTime)]
|
|
|
- initMyoption()
|
|
|
}
|
|
|
|
|
|
-const timeRange = ref('7d') // 修改默认选择为近一周
|
|
|
-const dateRange = ref<[Date, Date] | null>(null)
|
|
|
-
|
|
|
-const queryParams = reactive({
|
|
|
- startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
|
|
|
- endTime: Date.now() // 设置默认结束时间为当前时间
|
|
|
-})
|
|
|
-const backendData = ref([])
|
|
|
-const statusChartRef = ref() // 设备数量统计的图表
|
|
|
-const deviceMessageCountChartRef = ref() // 上下行消息量统计的图表
|
|
|
-const { t } = useI18n() // 国际化
|
|
|
-// 基础统计数据
|
|
|
-// TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
|
|
|
-const statsData = ref<IotStatisticsSummaryRespVO>({
|
|
|
- productCategoryCount: 0,
|
|
|
- productCount: 0,
|
|
|
- deviceCount: 0,
|
|
|
- deviceMessageCount: 0,
|
|
|
- productCategoryTodayCount: 0,
|
|
|
- productTodayCount: 0,
|
|
|
- deviceTodayCount: 0,
|
|
|
- deviceMessageTodayCount: 0,
|
|
|
- deviceOnlineCount: 0,
|
|
|
- deviceOfflineCount: 0,
|
|
|
- deviceInactiveCount: 0,
|
|
|
- productCategoryDeviceCounts: {}
|
|
|
-})
|
|
|
-const orderSevenData = ref({})
|
|
|
-const by = ref({
|
|
|
- todo: undefined,
|
|
|
- finished: undefined
|
|
|
-})
|
|
|
-const fill = ref({
|
|
|
- filledCount: undefined,
|
|
|
- unfilledCount: undefined
|
|
|
-})
|
|
|
-const inspectt = ref({
|
|
|
- finished: 0,
|
|
|
- todo: 0
|
|
|
-})
|
|
|
-const device = ref({
|
|
|
- total: undefined,
|
|
|
- today: undefined
|
|
|
-})
|
|
|
-const maintain = ref({
|
|
|
- total: undefined,
|
|
|
- today: undefined
|
|
|
-})
|
|
|
-const work = ref({
|
|
|
- total: undefined,
|
|
|
- today: undefined
|
|
|
-})
|
|
|
-const inspect = ref({
|
|
|
- total: undefined,
|
|
|
- today: undefined
|
|
|
-})
|
|
|
-
|
|
|
-const status = ref({
|
|
|
- finished: 0,
|
|
|
- todo: 0
|
|
|
-})
|
|
|
-const todayStatus = ref({
|
|
|
- finished: 0,
|
|
|
- todo: 0
|
|
|
-})
|
|
|
-const typeData = ref({})
|
|
|
-// 消息统计数据
|
|
|
-const messageStats = ref<IotStatisticsDeviceMessageSummaryRespVO>({
|
|
|
- upstreamCounts: {},
|
|
|
- downstreamCounts: {}
|
|
|
-})
|
|
|
-const mttr = ref(0)
|
|
|
-const safe = ref()
|
|
|
-/** 获取统计数据 */
|
|
|
-const getStats = async () => {
|
|
|
- // 获取基础统计数据
|
|
|
- IotStatApi.getDeviceCount().then((res) => {
|
|
|
- device.value = res
|
|
|
- })
|
|
|
- IotStatApi.getMaintainCount().then((res) => {
|
|
|
- maintain.value = res
|
|
|
- })
|
|
|
- IotStatApi.getMainWorkCount().then((res) => {
|
|
|
- work.value = res
|
|
|
- })
|
|
|
- IotStatApi.getInspectCount().then((res) => {
|
|
|
- inspect.value = res
|
|
|
- })
|
|
|
- IotStatApi.getMaintenanceStatus().then((res) => {
|
|
|
- status.value = res
|
|
|
- initCharts()
|
|
|
- })
|
|
|
- IotStatApi.getMaintenanceTodayStatus().then((res) => {
|
|
|
- todayStatus.value = res
|
|
|
- initCharts()
|
|
|
- })
|
|
|
- IotStatApi.getDeviceStatusCount().then((res) => {
|
|
|
- typeData.value = res
|
|
|
- initCharts()
|
|
|
- })
|
|
|
- IotStatApi.getSafeCount().then((res) => {
|
|
|
- safe.value = res
|
|
|
- })
|
|
|
- IotStatApi.getMttr().then((res) => {
|
|
|
- mttr.value = res
|
|
|
- })
|
|
|
- IotStatApi.getOrderSeven('156').then((res) => {
|
|
|
- orderSevenData.value = res
|
|
|
- initQxChart()
|
|
|
- })
|
|
|
-
|
|
|
- IotStatApi.getMaintenanceStatus().then((res) => {
|
|
|
- by.value = res
|
|
|
- })
|
|
|
- const fillQueryParams = reactive({
|
|
|
- startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
|
|
|
- endTime: Date.now(), // 设置默认结束时间为当前时间
|
|
|
- createTime: [],
|
|
|
- deptId: null, // 选中的部门ID
|
|
|
- status: null // 填写状态
|
|
|
- })
|
|
|
- IotStatApi.getInspectStatuss(fillQueryParams, '156').then((res) => {
|
|
|
- inspectt.value = res
|
|
|
- })
|
|
|
- fillQueryParams.deptId = '156'
|
|
|
- IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
|
|
|
- fill.value = res.totalList[0] || []
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-/** 初始化图表 */
|
|
|
-const initCharts = () => {
|
|
|
- // 设备数量统计
|
|
|
- echarts.init(statusChartRef.value).setOption({
|
|
|
- tooltip: {
|
|
|
- trigger: 'item'
|
|
|
- },
|
|
|
- legend: {
|
|
|
- top: '5%',
|
|
|
- right: '10%',
|
|
|
- align: 'left',
|
|
|
- orient: 'vertical',
|
|
|
- icon: 'circle',
|
|
|
- textStyle: {
|
|
|
- color: 'white'
|
|
|
- }
|
|
|
- },
|
|
|
- series: [
|
|
|
- {
|
|
|
- name: '',
|
|
|
- type: 'pie',
|
|
|
- radius: ['50%', '80%'],
|
|
|
- avoidLabelOverlap: false,
|
|
|
- center: ['30%', '50%'],
|
|
|
- label: {
|
|
|
- show: false,
|
|
|
- position: 'outside',
|
|
|
- color: 'white'
|
|
|
- },
|
|
|
- emphasis: {
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- fontSize: 20,
|
|
|
- fontWeight: 'bold'
|
|
|
- }
|
|
|
- },
|
|
|
- labelLine: {
|
|
|
- show: false
|
|
|
- },
|
|
|
- data: typeData.value
|
|
|
- }
|
|
|
- ]
|
|
|
- })
|
|
|
- //待执行
|
|
|
- // initGaugeChart(
|
|
|
- // writeTodayChartRef.value,
|
|
|
- // todayStatus.value.todo === undefined ? 0 : todayStatus.value.todo,
|
|
|
- // '#05b'
|
|
|
- // )
|
|
|
- // //已执行
|
|
|
- // initGaugeChart(
|
|
|
- // finishedTodayChartRef.value,
|
|
|
- // todayStatus.value.finished === undefined ? 0 : todayStatus.value.finished,
|
|
|
- // '#f50'
|
|
|
- // )
|
|
|
- // // 待执行
|
|
|
- // initGaugeChart(
|
|
|
- // writeChartRef.value,
|
|
|
- // status.value.todo === undefined ? 0 : status.value.todo,
|
|
|
- // '#05b'
|
|
|
- // )
|
|
|
- // //已执行
|
|
|
- // initGaugeChart(
|
|
|
- // finishedChartRef.value,
|
|
|
- // status.value.finished === undefined ? 0 : status.value.finished,
|
|
|
- // '#f50'
|
|
|
- // )
|
|
|
- // 消息量统计
|
|
|
- //initMessageChart()
|
|
|
-}
|
|
|
-
|
|
|
-/** 初始化仪表盘图表 */
|
|
|
-const initGaugeChart = (el: any, value: number, color: string) => {
|
|
|
- echarts.init(el).setOption({
|
|
|
- series: [
|
|
|
- {
|
|
|
- type: 'gauge',
|
|
|
- startAngle: 360,
|
|
|
- endAngle: 0,
|
|
|
- min: 0,
|
|
|
- max: statsData.value.deviceCount || 100, // 使用设备总数作为最大值
|
|
|
- progress: {
|
|
|
- show: true,
|
|
|
- width: 12,
|
|
|
- itemStyle: {
|
|
|
- color: color
|
|
|
- }
|
|
|
- },
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- width: 12,
|
|
|
- color: [[1, '#E5E7EB']]
|
|
|
- }
|
|
|
- },
|
|
|
- axisTick: { show: false },
|
|
|
- splitLine: { show: false },
|
|
|
- axisLabel: { show: false },
|
|
|
- pointer: { show: false },
|
|
|
- anchor: { show: false },
|
|
|
- title: { show: false },
|
|
|
- detail: {
|
|
|
- valueAnimation: true,
|
|
|
- fontSize: 24,
|
|
|
- fontWeight: 'bold',
|
|
|
- fontFamily: 'Inter, sans-serif',
|
|
|
- color: color,
|
|
|
- offsetCenter: [0, '0'],
|
|
|
- formatter: (value: number) => {
|
|
|
- return `${value} `
|
|
|
- }
|
|
|
- },
|
|
|
- data: [{ value: value }]
|
|
|
- }
|
|
|
- ]
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-/** 初始化消息统计图表 */
|
|
|
-const initMessageChart = () => {
|
|
|
- // 获取所有时间戳并排序
|
|
|
- // TODO @super:一些 idea 里的红色报错,要去处理掉噢。
|
|
|
- const timestamps = Array.from(
|
|
|
- new Set([
|
|
|
- ...messageStats.value.upstreamCounts.map((item) => Number(Object.keys(item)[0])),
|
|
|
- ...messageStats.value.downstreamCounts.map((item) => Number(Object.keys(item)[0]))
|
|
|
- ])
|
|
|
- ).sort((a, b) => a - b) // 确保时间戳从小到大排序
|
|
|
-
|
|
|
- // 准备数据
|
|
|
- const xdata = timestamps.map((ts) => formatDate(ts, 'YYYY-MM-DD HH:mm'))
|
|
|
- const upData = timestamps.map((ts) => {
|
|
|
- const item = messageStats.value.upstreamCounts.find(
|
|
|
- (count) => Number(Object.keys(count)[0]) === ts
|
|
|
- )
|
|
|
- return item ? Object.values(item)[0] : 0
|
|
|
- })
|
|
|
- const downData = timestamps.map((ts) => {
|
|
|
- const item = messageStats.value.downstreamCounts.find(
|
|
|
- (count) => Number(Object.keys(count)[0]) === ts
|
|
|
- )
|
|
|
- return item ? Object.values(item)[0] : 0
|
|
|
- })
|
|
|
-
|
|
|
- // 配置图表
|
|
|
- echarts.init(deviceMessageCountChartRef.value).setOption({
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis',
|
|
|
- backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
|
- borderColor: '#E5E7EB',
|
|
|
- textStyle: {
|
|
|
- color: '#374151'
|
|
|
- }
|
|
|
- },
|
|
|
- legend: {
|
|
|
- data: ['上行消息量', '下行消息量'],
|
|
|
- textStyle: {
|
|
|
- color: '#374151',
|
|
|
- fontWeight: 500
|
|
|
- }
|
|
|
- },
|
|
|
- grid: {
|
|
|
- left: '3%',
|
|
|
- right: '4%',
|
|
|
- bottom: '3%',
|
|
|
- containLabel: true
|
|
|
- },
|
|
|
- xAxis: {
|
|
|
- type: 'category',
|
|
|
- boundaryGap: false,
|
|
|
- data: xdata,
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#E5E7EB'
|
|
|
- }
|
|
|
- },
|
|
|
- axisLabel: {
|
|
|
- color: '#6B7280'
|
|
|
- }
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- type: 'value',
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#E5E7EB'
|
|
|
- }
|
|
|
- },
|
|
|
- axisLabel: {
|
|
|
- color: '#6B7280'
|
|
|
- },
|
|
|
- splitLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#F3F4F6'
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- series: [
|
|
|
- {
|
|
|
- name: '上行消息量',
|
|
|
- type: 'line',
|
|
|
- smooth: true, // 添加平滑曲线
|
|
|
- data: upData,
|
|
|
- itemStyle: {
|
|
|
- color: '#3B82F6'
|
|
|
- },
|
|
|
- lineStyle: {
|
|
|
- width: 2
|
|
|
- },
|
|
|
- areaStyle: {
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
- { offset: 0, color: 'rgba(59, 130, 246, 0.2)' },
|
|
|
- { offset: 1, color: 'rgba(59, 130, 246, 0)' }
|
|
|
- ])
|
|
|
- }
|
|
|
- },
|
|
|
- {
|
|
|
- name: '下行消息量',
|
|
|
- type: 'line',
|
|
|
- smooth: true, // 添加平滑曲线
|
|
|
- data: downData,
|
|
|
- itemStyle: {
|
|
|
- color: '#10B981'
|
|
|
- },
|
|
|
- lineStyle: {
|
|
|
- width: 2
|
|
|
- },
|
|
|
- areaStyle: {
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
- { offset: 0, color: 'rgba(16, 185, 129, 0.2)' },
|
|
|
- { offset: 1, color: 'rgba(16, 185, 129, 0)' }
|
|
|
- ])
|
|
|
- }
|
|
|
- }
|
|
|
- ]
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-const chartContainer = ref(null)
|
|
|
-let chartInstance = null
|
|
|
-
|
|
|
-// 生成过去12个月份的标签 (格式: YYYY-MM)
|
|
|
-const generateMonthLabels = () => {
|
|
|
- const months = []
|
|
|
- const date = new Date()
|
|
|
- for (let i = 11; i >= 0; i--) {
|
|
|
- const tempDate = new Date(date.getFullYear(), date.getMonth() - i, 1)
|
|
|
- const year = tempDate.getFullYear()
|
|
|
- const month = String(tempDate.getMonth() + 1).padStart(2, '0')
|
|
|
- months.push(`${year}-${month}`)
|
|
|
- }
|
|
|
- return months
|
|
|
-}
|
|
|
-
|
|
|
-// 模拟数据获取
|
|
|
-const fetchChartData = async () => {
|
|
|
- // 模拟异步请求
|
|
|
- return new Promise((resolve) => {
|
|
|
- setTimeout(() => {
|
|
|
- resolve({
|
|
|
- months: generateMonthLabels(),
|
|
|
- faults: [20, 30, 100, 40, 20, 50, 70, 80, 60, 90, 100, 100],
|
|
|
- repairs: [10, 30, 90, 30, 10, 20, 60, 50, 22, 34, 70, 85]
|
|
|
- })
|
|
|
- }, 300)
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-// 初始化图表配置
|
|
|
-const initChart = async () => {
|
|
|
- if (!chartContainer.value) return
|
|
|
-
|
|
|
- // 获取数据
|
|
|
- const { months, faults, repairs } = await fetchChartData()
|
|
|
-
|
|
|
- // ECharts配置
|
|
|
- const option = {
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis',
|
|
|
- axisPointer: {
|
|
|
- type: 'shadow'
|
|
|
- },
|
|
|
- formatter: (params) => {
|
|
|
- return `${params[0].axisValue}<br/>
|
|
|
- ${params[0].marker} ${params[0].seriesName}: ${params[0].value}`
|
|
|
- }
|
|
|
- },
|
|
|
- legend: {
|
|
|
- data: ['保养工单数量'],
|
|
|
- top: 25
|
|
|
- },
|
|
|
- grid: {
|
|
|
- left: '3%',
|
|
|
- right: '4%',
|
|
|
- bottom: '3%',
|
|
|
- containLabel: true
|
|
|
- },
|
|
|
- xAxis: {
|
|
|
- type: 'category',
|
|
|
- data: months,
|
|
|
- axisLabel: {
|
|
|
- rotate: 45,
|
|
|
- margin: 15
|
|
|
- }
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- type: 'value',
|
|
|
- axisLabel: {
|
|
|
- formatter: (value) => Math.floor(value).toString()
|
|
|
- }
|
|
|
- },
|
|
|
- series: [
|
|
|
- {
|
|
|
- name: '保养工单数量',
|
|
|
- type: 'bar',
|
|
|
- itemStyle: {
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
- { offset: 0, color: '#2196df' },
|
|
|
- { offset: 1, color: '#2196df' }
|
|
|
- ])
|
|
|
- },
|
|
|
- emphasis: {
|
|
|
- focus: 'series'
|
|
|
- },
|
|
|
- data: repairs
|
|
|
- }
|
|
|
- ]
|
|
|
- }
|
|
|
-
|
|
|
- // 初始化图表
|
|
|
- chartInstance = echarts.init(chartContainer.value)
|
|
|
- chartInstance.setOption(option)
|
|
|
-
|
|
|
- // 窗口缩放监听
|
|
|
- window.addEventListener('resize', handleResize)
|
|
|
- handleResize()
|
|
|
-}
|
|
|
-
|
|
|
-// 自适应调整
|
|
|
-const handleResize = () => {
|
|
|
- chartInstance?.resize()
|
|
|
-}
|
|
|
-
|
|
|
-const topContainer = ref(null)
|
|
|
-let topInstance = null
|
|
|
-// 响应式容器尺寸
|
|
|
-const { width, height } = useElementSize(topContainer)
|
|
|
-// 处理数据(排序+限制5条)
|
|
|
-const processedData = () => {
|
|
|
- const data = IotStatApi.getDeviceTypeCount()
|
|
|
- backendData.value = data
|
|
|
- return [...backendData.value].sort((a, b) => a.value - b.value)
|
|
|
-}
|
|
|
-
|
|
|
-const fetchTop = () => {
|
|
|
- IotStatApi.getDeviceTypeCount().then((res) => {
|
|
|
- backendData.value = res
|
|
|
- })
|
|
|
-}
|
|
|
-// 初始化图表配置
|
|
|
-const getTopOption = () => {
|
|
|
- // backendData.value = data
|
|
|
- const data = backendData.value.sort((a, b) => a.value - b.value)
|
|
|
- return {
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis',
|
|
|
- axisPointer: { type: 'shadow' },
|
|
|
- formatter: (params) => {
|
|
|
- const item = params[0]
|
|
|
- return `${item.name}<br/>${item.marker} ${item.value.toLocaleString()}`
|
|
|
- }
|
|
|
- },
|
|
|
- grid: {
|
|
|
- height: '200px',
|
|
|
- left: '6%',
|
|
|
- right: '6%',
|
|
|
- bottom: '5%',
|
|
|
- containLabel: true
|
|
|
- },
|
|
|
- xAxis: {
|
|
|
- type: 'value',
|
|
|
- axisLabel: {
|
|
|
- color: 'white',
|
|
|
- formatter: (value) => {
|
|
|
- if (value >= 10000) return `${(value / 10000).toFixed(1)}万`
|
|
|
- return value.toLocaleString()
|
|
|
- }
|
|
|
- },
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#B6C8DA' // X轴线白色半透明
|
|
|
- }
|
|
|
- },
|
|
|
- splitLine: {
|
|
|
- show: true, // 显示水平网格线(默认显示)
|
|
|
- lineStyle: {
|
|
|
- // 水平网格线颜色(可设置为纯色或带透明度的颜色)
|
|
|
- color: '#B6C8DA', // 浅灰色半透明
|
|
|
- // 可选:设置线条类型(实线/虚线/点线)
|
|
|
- type: 'dashed'
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- type: 'category',
|
|
|
- data: data.map((item) => item.category),
|
|
|
- axisTick: { show: false },
|
|
|
- axisLabel: { color: '#B6C8DA' },
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#B6C8DA' // X轴线白色半透明
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- series: [
|
|
|
- {
|
|
|
- type: 'bar',
|
|
|
- data: data.map((item) => item.value),
|
|
|
- itemStyle: {
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|
|
- { offset: 0, color: '#83bff6' },
|
|
|
- { offset: 0.7, color: '#188df0' },
|
|
|
- { offset: 1, color: '#188df0' }
|
|
|
- ]),
|
|
|
- borderRadius: [0, 8, 8, 0]
|
|
|
- },
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- position: 'right',
|
|
|
- formatter: '{@value}',
|
|
|
- color: 'white',
|
|
|
- fontWeight: 'bold'
|
|
|
- },
|
|
|
- emphasis: {
|
|
|
- itemStyle: {
|
|
|
- shadowBlur: 10,
|
|
|
- shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- ]
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 初始化图表
|
|
|
-const initTopChart = async () => {
|
|
|
- await IotStatApi.getDeviceTypeCount('yf').then((res) => {
|
|
|
- backendData.value = res
|
|
|
- })
|
|
|
- if (!topContainer.value) return
|
|
|
- topInstance = echarts.init(topContainer.value)
|
|
|
- updateTopChart()
|
|
|
-}
|
|
|
-
|
|
|
-// 更新图表
|
|
|
-const updateTopChart = () => {
|
|
|
- if (!topInstance) return
|
|
|
- topInstance.setOption(getTopOption())
|
|
|
-}
|
|
|
-
|
|
|
-// 自适应调整
|
|
|
-watch([width, height], () => {
|
|
|
- topInstance?.resize()
|
|
|
-})
|
|
|
-
|
|
|
-// 监听数据变化
|
|
|
-watch(
|
|
|
- backendData,
|
|
|
- () => {
|
|
|
- updateTopChart()
|
|
|
- },
|
|
|
- { deep: true }
|
|
|
-)
|
|
|
-
|
|
|
-const activeDom = ref(null)
|
|
|
-let activeInstance = null
|
|
|
-
|
|
|
-// 模拟后端数据结构
|
|
|
-// const activeData = ref([
|
|
|
-// { department: '瑞恒兴域', total: 356, active: 278 },
|
|
|
-// { department: '瑞鹰国际', total: 284, active: 192 },
|
|
|
-// { department: '四川瑞都', total: 432, active: 325 },
|
|
|
-// { department: '运营中心', total: 157, active: 89 }
|
|
|
-// ])
|
|
|
-const activeData = ref([])
|
|
|
-const initActiveChart = async (total: any, active: any, people: any) => {
|
|
|
- if (!activeDom.value) return
|
|
|
- activeData.value = await IotStatApi.getDeptCount()
|
|
|
- activeInstance = echarts.init(activeDom.value)
|
|
|
-
|
|
|
- const option = {
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis',
|
|
|
- axisPointer: { type: 'shadow' }, //:ml-citation{ref="1,7" data="citationList"}
|
|
|
- formatter: (params) => `
|
|
|
- ${params[0].name}<br/>
|
|
|
- ${params[0].marker} ${total}: ${params[0].value}<br/>
|
|
|
- ${params[1].marker} ${active}: ${params[1].value}
|
|
|
- `
|
|
|
- },
|
|
|
- legend: {
|
|
|
- data: [total, active],
|
|
|
- top: 30,
|
|
|
- textStyle: {
|
|
|
- color: '#B6C8DA'
|
|
|
- }
|
|
|
- },
|
|
|
- grid: {
|
|
|
- left: '3%',
|
|
|
- right: '4%',
|
|
|
- bottom: '3%',
|
|
|
- containLabel: true //:ml-citation{ref="2,7" data="citationList"}
|
|
|
- },
|
|
|
- xAxis: {
|
|
|
- type: 'category',
|
|
|
- data: activeData.value.map((item) => item.department),
|
|
|
- axisLabel: {
|
|
|
- color: '#B6C8DA',
|
|
|
- interval: 0,
|
|
|
- rotate: 0 //:ml-citation{ref="5" data="citationList"}
|
|
|
- },
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#B6C8DA'
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- type: 'value',
|
|
|
- name: people,
|
|
|
- axisLabel: {
|
|
|
- color: '#B6C8DA'
|
|
|
- },
|
|
|
- splitLine: {
|
|
|
- show: true, // 显示水平网格线(默认显示)
|
|
|
- lineStyle: {
|
|
|
- // 水平网格线颜色(可设置为纯色或带透明度的颜色)
|
|
|
- color: '#457794', // 浅灰色半透明
|
|
|
- // 可选:设置线条类型(实线/虚线/点线)
|
|
|
- type: 'dashed'
|
|
|
- }
|
|
|
- },
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#B6C8DA'
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- series: [
|
|
|
- {
|
|
|
- name: total,
|
|
|
- type: 'bar',
|
|
|
- data: activeData.value.map((item) => item.total),
|
|
|
- itemStyle: {
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
- { offset: 0, color: '#5470c6' },
|
|
|
- { offset: 1, color: '#83bff6' }
|
|
|
- ])
|
|
|
- },
|
|
|
- barWidth: 30
|
|
|
- },
|
|
|
- {
|
|
|
- name: active,
|
|
|
- type: 'bar',
|
|
|
- data: activeData.value.map((item) => item.active),
|
|
|
- itemStyle: {
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
- { offset: 0, color: '#91cc75' },
|
|
|
- { offset: 1, color: '#e6f4d2' }
|
|
|
- ])
|
|
|
- },
|
|
|
- barWidth: 30
|
|
|
- }
|
|
|
- ]
|
|
|
- }
|
|
|
-
|
|
|
- activeInstance.setOption(option)
|
|
|
-}
|
|
|
-
|
|
|
-const qxRef = ref(null)
|
|
|
-let qxInstance = null
|
|
|
-
|
|
|
-// 生成近12个月份 (包含当年和去年)
|
|
|
-const generateMonths = () => {
|
|
|
- const months = []
|
|
|
- const date = new Date()
|
|
|
- date.setMonth(date.getMonth() + 1, 1) // 从下个月开始倒推
|
|
|
-
|
|
|
- for (let i = 0; i < 12; i++) {
|
|
|
- date.setMonth(date.getMonth() - 1)
|
|
|
- months.push(`${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`)
|
|
|
- }
|
|
|
- return months.reverse()
|
|
|
-}
|
|
|
-
|
|
|
-const initQxChart = () => {
|
|
|
- if (!qxRef.value) return
|
|
|
- qxInstance = echarts.init(qxRef.value)
|
|
|
- const option = {
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis',
|
|
|
- axisPointer: {
|
|
|
- type: 'cross',
|
|
|
- label: {
|
|
|
- backgroundColor: '#6a7985'
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- legend: {
|
|
|
- data: orderSevenData.value.series.map((item) => item.name),
|
|
|
- top: 30,
|
|
|
- textStyle: {
|
|
|
- color: '#B6C8DA'
|
|
|
- }
|
|
|
- },
|
|
|
- grid: {
|
|
|
- left: '3%',
|
|
|
- right: '4%',
|
|
|
- bottom: '3%',
|
|
|
- containLabel: true
|
|
|
- },
|
|
|
- xAxis: {
|
|
|
- type: 'category',
|
|
|
- boundaryGap: false,
|
|
|
- data: orderSevenData.value.xAxis,
|
|
|
- axisLabel: {
|
|
|
- color: '#B6C8DA',
|
|
|
- formatter: (value) => value.split('-').join('/') // 显示为 2023/01
|
|
|
- },
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#B6C8DA'
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- yAxis: [
|
|
|
- {
|
|
|
- type: 'value',
|
|
|
- axisLabel: {
|
|
|
- color: '#B6C8DA',
|
|
|
- formatter: '{value}'
|
|
|
- },
|
|
|
- splitLine: {
|
|
|
- show: true, // 显示水平网格线(默认显示)
|
|
|
- lineStyle: {
|
|
|
- // 水平网格线颜色(可设置为纯色或带透明度的颜色)
|
|
|
- color: '#457794', // 浅灰色半透明
|
|
|
- // 可选:设置线条类型(实线/虚线/点线)
|
|
|
- type: 'dashed'
|
|
|
- }
|
|
|
- },
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#B6C8DA'
|
|
|
- }
|
|
|
- },
|
|
|
- position: 'left' // 左侧 Y 轴
|
|
|
- },
|
|
|
- {
|
|
|
- type: 'value',
|
|
|
- axisLabel: {
|
|
|
- formatter: '{value}'
|
|
|
- },
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#B6C8DA'
|
|
|
- }
|
|
|
- },
|
|
|
- position: 'right', // 右侧 Y 轴
|
|
|
- splitLine: {
|
|
|
- show: false // 隐藏右侧 Y 轴的分割线
|
|
|
- }
|
|
|
- }
|
|
|
- ],
|
|
|
- series: orderSevenData.value.series.map((item, index) => {
|
|
|
- // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
|
|
|
- const yAxisIndex = index < 2 ? 0 : 1
|
|
|
- return {
|
|
|
- name: item.name,
|
|
|
- type: 'line',
|
|
|
- smooth: true,
|
|
|
- symbol: 'circle',
|
|
|
- symbolSize: 8,
|
|
|
- itemStyle: {
|
|
|
- color: ['#5470c6', '#f1d209', '#e14f0f', '#91cc75'][index]
|
|
|
- },
|
|
|
- areaStyle: {
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
- { offset: 0, color: 'rgba(84,112,198,0.4)' },
|
|
|
- { offset: 1, color: 'rgba(84,112,198,0.1)' }
|
|
|
- ])
|
|
|
- },
|
|
|
- data: item.data,
|
|
|
- yAxisIndex: yAxisIndex // 指定使用的 Y 轴
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- qxInstance.setOption(option)
|
|
|
-}
|
|
|
-
|
|
|
-// 响应式调整
|
|
|
-const resizeQxChart = () => qxInstance?.resize()
|
|
|
-/** 初始化 */
|
|
|
onMounted(() => {
|
|
|
- getStats()
|
|
|
- // initChart()
|
|
|
- initTopChart()
|
|
|
- const localeStore = useLocaleStore()
|
|
|
- const lang = localeStore.getCurrentLocale.lang
|
|
|
- if (lang === 'zh-CN') {
|
|
|
- initActiveChart('总人数', '活跃人数', '人数')
|
|
|
- } else if (lang === 'en') {
|
|
|
- initActiveChart('totalPeople', 'activePeople', 'count')
|
|
|
+ nextTick(updateScale)
|
|
|
+ resizeObserver = new ResizeObserver(updateScale)
|
|
|
+ if (wrapperRef.value) {
|
|
|
+ resizeObserver.observe(wrapperRef.value)
|
|
|
}
|
|
|
- initMyoption()
|
|
|
|
|
|
- // initQxChart()
|
|
|
- window.addEventListener('resize', resizeQxChart)
|
|
|
- // fetchTop()
|
|
|
- window.addEventListener('resize', () => topInstance?.resize())
|
|
|
+ window.addEventListener('resize', updateScale)
|
|
|
})
|
|
|
-onBeforeUnmount(() => {
|
|
|
- chartInstance?.dispose()
|
|
|
- window.removeEventListener('resize', () => chartInstance?.resize())
|
|
|
- topInstance?.dispose()
|
|
|
- window.removeEventListener('resize', handleResize)
|
|
|
- qxInstance?.dispose()
|
|
|
- window.removeEventListener('resize', resizeQxChart)
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ resizeObserver?.disconnect()
|
|
|
+ window.removeEventListener('resize', updateScale)
|
|
|
+ cancelAnimationFrame(resizeRaf)
|
|
|
})
|
|
|
</script>
|
|
|
-
|
|
|
+<template>
|
|
|
+ <div ref="wrapperRef" class="bg absolute top-0 left-0 size-full z-10">
|
|
|
+ <div class="mx-a overflow-hidden" :style="targetWrapperStyle">
|
|
|
+ <div class="bg" :style="targetAreaStyle">
|
|
|
+ <header class="header">{{ company }}</header>
|
|
|
+ <div class="mt-3 px-5">
|
|
|
+ <hsummary class="kb-stage-card kb-stage-card--1" />
|
|
|
+ <div class="w-full h-96 grid grid-rows-2 grid-cols-3 gap-3 mt-3">
|
|
|
+ <hmttr class="kb-stage-card kb-stage-card--2" />
|
|
|
+
|
|
|
+ <hdeviceType class="row-span-2 kb-stage-card kb-stage-card--4" />
|
|
|
+ <dayfinish class="row-span-2 kb-stage-card kb-stage-card--5" />
|
|
|
+ <hsafe class="kb-stage-card kb-stage-card--3" />
|
|
|
+ </div>
|
|
|
+ <div class="w-full h-107 grid grid-cols-2 gap-3 mt-3">
|
|
|
+ <hdeviceStatus class="kb-stage-card kb-stage-card--6" />
|
|
|
+ <horderTrend class="kb-stage-card kb-stage-card--7" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
<style lang="scss" scoped>
|
|
|
-.page-container {
|
|
|
- background-color: #3a6fa3;
|
|
|
- min-height: 90vh;
|
|
|
- padding: 20px;
|
|
|
-}
|
|
|
-
|
|
|
-.summary {
|
|
|
- margin-bottom: 20px;
|
|
|
-}
|
|
|
-
|
|
|
-::v-deep .chart-card {
|
|
|
- background-color: rgba(0, 0, 0, 0.3);
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
|
|
|
- transition: all 0.3s ease;
|
|
|
- border: none;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.safety-days-card {
|
|
|
- .safety-days-content {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- height: 150px;
|
|
|
- position: relative;
|
|
|
-
|
|
|
- .days-number {
|
|
|
- font-size: 58px;
|
|
|
- font-weight: bold;
|
|
|
- color: darkorange;
|
|
|
- line-height: 1;
|
|
|
- transition: all 0.3s ease;
|
|
|
- }
|
|
|
-
|
|
|
- .days-number:hover {
|
|
|
- transform: scale(1.05);
|
|
|
- }
|
|
|
-
|
|
|
- .days-label {
|
|
|
- font-size: 20px;
|
|
|
- color: white;
|
|
|
- margin-top: 8px;
|
|
|
- }
|
|
|
-
|
|
|
- .safety-desc {
|
|
|
- font-size: 14px;
|
|
|
- color: #999;
|
|
|
- position: absolute;
|
|
|
- bottom: 10px;
|
|
|
- text-align: center;
|
|
|
- width: 90%;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-@media (max-width: 768px) {
|
|
|
- .page-container {
|
|
|
- padding: 10px;
|
|
|
- }
|
|
|
-}
|
|
|
-::v-deep .el-card__header {
|
|
|
- border-bottom: none !important;
|
|
|
- padding-bottom: 0;
|
|
|
-}
|
|
|
+@import url('@/styles/kb.scss');
|
|
|
</style>
|