|
@@ -0,0 +1,1026 @@
|
|
|
|
+<template>
|
|
|
|
+ <el-row :gutter="16" class="summary">
|
|
|
|
+ <!-- 原有的统计卡片部分保持不变 -->
|
|
|
|
+ <el-col v-loading="loading" :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="设备数"
|
|
|
|
+ />
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col v-loading="loading" :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="维修工单"
|
|
|
|
+ />
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col v-loading="loading" :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="运行未填写"
|
|
|
|
+ />
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col v-loading="loading" :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="运行已填写"
|
|
|
|
+ />
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col v-loading="loading" :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="未执行保养"
|
|
|
|
+ />
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col v-loading="loading" :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="已执行保养"
|
|
|
|
+ />
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col v-loading="loading" :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="待填写巡检"
|
|
|
|
+ />
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col v-loading="loading" :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="已填写巡检"
|
|
|
|
+ />
|
|
|
|
+ </el-col>
|
|
|
|
+ <!-- 其他统计卡片... -->
|
|
|
|
+ </el-row>
|
|
|
|
+ <!-- 第二行:图表行 -->
|
|
|
|
+ <el-row :gutter="16" class="mb-4 mt-3">
|
|
|
|
+ <el-col :span="6">
|
|
|
|
+ <el-card class="chart-card" shadow="never">
|
|
|
|
+ <template #header>
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <span class="text-base font-medium text-gray-600">设备状态统计</span>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ <div ref="statusChartRef" class="h-[320px]"></div>
|
|
|
|
+ </el-card>
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col :span="7">
|
|
|
|
+ <el-card class="chart-card" shadow="never">
|
|
|
|
+ <template #header>
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <span class="text-base font-medium text-gray-600">设备类别TOP5数量</span>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ <div ref="topContainer" class="h-[320px]"></div>
|
|
|
|
+ </el-card>
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col :span="11">
|
|
|
|
+ <el-card class="chart-card" shadow="never">
|
|
|
|
+ <template #header>
|
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
|
+ <span class="text-base font-medium text-gray-600">工单数量情况</span>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ <div ref="qxRef" class="h-[320px]"></div>
|
|
|
|
+ </el-card>
|
|
|
|
+ </el-col>
|
|
|
|
+ </el-row>
|
|
|
|
+
|
|
|
|
+ <!-- 新增:修井完成情况图表 -->
|
|
|
|
+ <el-row :gutter="16" class="mb-4">
|
|
|
|
+ <el-col :span="8">
|
|
|
|
+ <div class="flex flex-col justify-between">
|
|
|
|
+ <el-card class="chart-card" shadow="never">
|
|
|
|
+ <template #header>
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <span class="text-base font-medium text-gray-600">修井完成情况</span>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ <div ref="repairWellChartRef" class="h-[150px]"></div>
|
|
|
|
+ </el-card>
|
|
|
|
+ <el-card class="chart-card mt-1" shadow="never">
|
|
|
|
+ <template #header>
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <span class="text-base font-medium text-gray-600">钻井完成情况</span>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ <div ref="drillingWellChartRef" class="h-[150px]"></div>
|
|
|
|
+ </el-card>
|
|
|
|
+ </div>
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col :span="8">
|
|
|
|
+ <el-card class="chart-card" shadow="never">
|
|
|
|
+ <template #header>
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <span class="text-base font-medium text-gray-600">钻井工作量情况</span>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ <div ref="drillingWorkloadChartRef" class="h-[400px]"></div>
|
|
|
|
+ </el-card>
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col :span="8">
|
|
|
|
+ <el-card class="chart-card mt-1" shadow="never">
|
|
|
|
+ <template #header>
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <span class="text-base font-medium text-gray-600">修井工作量情况</span>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ <div ref="repairWorkloadChartRef" class="h-[400px]"></div>
|
|
|
|
+ </el-card>
|
|
|
|
+ </el-col>
|
|
|
|
+ </el-row>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+</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'
|
|
|
|
+import { formatDate } from '@/utils/formatTime'
|
|
|
|
+import { IotStatApi } from '@/api/pms/stat'
|
|
|
|
+import SummaryCard from "@/components/SummaryCard/index.vue";
|
|
|
|
+import {reactive, ref, onMounted, onBeforeUnmount, watch} from "vue";
|
|
|
|
+import {useUserStore} from "@/store/modules/user";
|
|
|
|
+
|
|
|
|
+// TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
|
|
|
|
+
|
|
|
|
+/** IoT 首页 */
|
|
|
|
+defineOptions({ name: 'IotRyStat' })
|
|
|
|
+
|
|
|
|
+// 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 dateRange = ref<[Date, Date] | null>(null)
|
|
|
|
+const by = ref({
|
|
|
|
+ todo: undefined,
|
|
|
|
+ finished: undefined
|
|
|
|
+})
|
|
|
|
+const fill = ref({
|
|
|
|
+ filledCount: undefined,
|
|
|
|
+ unfilledCount: undefined
|
|
|
|
+})
|
|
|
|
+const inspectt = ref({
|
|
|
|
+ finished: 0,
|
|
|
|
+ todo: 0
|
|
|
|
+})
|
|
|
|
+const queryParams = reactive({
|
|
|
|
+ startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
|
|
|
|
+ endTime: Date.now() // 设置默认结束时间为当前时间
|
|
|
|
+})
|
|
|
|
+const backendData = ref([])
|
|
|
|
+const statusChartRef = ref() // 设备数量统计的图表
|
|
|
|
+const materialChartRef = ref() // 设备数量统计的图表
|
|
|
|
+const drillingWorkloadChartRef = ref()
|
|
|
|
+const repairWorkloadChartRef = ref()
|
|
|
|
+// 基础统计数据
|
|
|
|
+// 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 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 materialData = ref({})
|
|
|
|
+const orderSevenData = ref({})
|
|
|
|
+const safe = ref()
|
|
|
|
+
|
|
|
|
+// 新增:修井完成情况数据
|
|
|
|
+const repairWellData = ref({
|
|
|
|
+ xAxis: ['小修10队', '小修8队', '小修9队'],
|
|
|
|
+ series: [
|
|
|
|
+ { name: '日累完成井数', data: [10, 15, 20] },
|
|
|
|
+ { name: '月累完成井数', data: [50, 60, 70] },
|
|
|
|
+ { name: '年累完成井数', data: [200, 220, 250] }
|
|
|
|
+ ]
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 新增:钻井完成情况数据
|
|
|
|
+const drillingWellData = ref({
|
|
|
|
+ xAxis: ['50010队', '四川宝石带压作业队'],
|
|
|
|
+ series: [
|
|
|
|
+ { name: '日累完成井数', data: [5, 8] },
|
|
|
|
+ { name: '月累完成井数', data: [40, 30] },
|
|
|
|
+ { name: '年累完成井数', data: [180,150 ] }
|
|
|
|
+ ]
|
|
|
|
+})
|
|
|
|
+const drillingWorkloadData = ref({
|
|
|
|
+ xAxis: ['SCP项目部', '伊拉克项目部', '陕西项目部'],
|
|
|
|
+ series: [
|
|
|
|
+ { name: '日累进尺', data: [150,100, 200] },
|
|
|
|
+ { name: '月累进尺', data: [1000, 1200, 1500] },
|
|
|
|
+ { name: '年累进尺', data: [2000,5000, 10000] }
|
|
|
|
+ ]
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 新增:修井工作量情况数据
|
|
|
|
+const repairWorkloadData = ref({
|
|
|
|
+ xAxis: ['SCP项目部', '伊拉克项目部', '陕西项目部'],
|
|
|
|
+ series: [
|
|
|
|
+ { name: '日累进尺', data: [120,80, 160] },
|
|
|
|
+ { name: '月累进尺', data: [1300,800, 1000] },
|
|
|
|
+ { name: '年累进尺', data: [4000, 5000, 7000] }
|
|
|
|
+ ]
|
|
|
|
+})
|
|
|
|
+const repairWellChartRef = ref()
|
|
|
|
+const drillingWellChartRef = ref()
|
|
|
|
+
|
|
|
|
+/** 获取统计数据 */
|
|
|
|
+const getStats = () => {
|
|
|
|
+ initYwcbChart()
|
|
|
|
+ // 获取基础统计数据
|
|
|
|
+ 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
|
|
|
|
+ initTopChart()
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ IotStatApi.getDeviceStatusCount().then((res) => {
|
|
|
|
+ typeData.value = res
|
|
|
|
+ initDeviceStatusCharts()
|
|
|
|
+ })
|
|
|
|
+ IotStatApi.getSafeCount().then((res) => {
|
|
|
|
+ safe.value = res
|
|
|
|
+ })
|
|
|
|
+ IotStatApi.getMaterial().then((res) => {
|
|
|
|
+ materialData.value = res
|
|
|
|
+ initMaterials()
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ IotStatApi.getOrderSeven().then((res) => {
|
|
|
|
+ orderSevenData.value = res
|
|
|
|
+ initQxChart();
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ IotStatApi.getMaintenanceStatus().then((res) => {
|
|
|
|
+ by.value = res
|
|
|
|
+ initMaintenanceChart()
|
|
|
|
+ })
|
|
|
|
+ const fillQueryParams = reactive({
|
|
|
|
+ startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
|
|
|
|
+ endTime: Date.now(), // 设置默认结束时间为当前时间
|
|
|
|
+ createTime: [],
|
|
|
|
+ deptId: null, // 选中的部门ID
|
|
|
|
+ status: null // 填写状态
|
|
|
|
+ })
|
|
|
|
+ IotStatApi.getInspectStatus(fillQueryParams).then((res) => {
|
|
|
|
+ inspectt.value = res
|
|
|
|
+ })
|
|
|
|
+ fillQueryParams.deptId = useUserStore().getUser.deptId
|
|
|
|
+ IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
|
|
|
|
+ fill.value = res.totalList[0] || []
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+const initDrillingWorkloadChart = () => {
|
|
|
|
+ if (!drillingWorkloadChartRef.value) return
|
|
|
|
+ const chart = echarts.init(drillingWorkloadChartRef.value)
|
|
|
|
+ const option = {
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'axis',
|
|
|
|
+ axisPointer: {
|
|
|
|
+ type: 'shadow'
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ legend: {
|
|
|
|
+ data: drillingWorkloadData.value.series.map(item => item.name)
|
|
|
|
+ },
|
|
|
|
+ grid: {
|
|
|
|
+ left: '3%',
|
|
|
|
+ right: '4%',
|
|
|
|
+ bottom: '3%',
|
|
|
|
+ containLabel: true
|
|
|
|
+ },
|
|
|
|
+ xAxis: {
|
|
|
|
+ type: 'category',
|
|
|
|
+ data: drillingWorkloadData.value.xAxis
|
|
|
|
+ },
|
|
|
|
+ yAxis: {
|
|
|
|
+ type: 'value',
|
|
|
|
+ name: '进尺(米)'
|
|
|
|
+ },
|
|
|
|
+ series: drillingWorkloadData.value.series.map(item => ({
|
|
|
|
+ name: item.name,
|
|
|
|
+ type: 'bar',
|
|
|
|
+ data: item.data
|
|
|
|
+ }))
|
|
|
|
+ }
|
|
|
|
+ chart.setOption(option)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 新增:初始化修井工作量情况图表
|
|
|
|
+const initRepairWorkloadChart = () => {
|
|
|
|
+ if (!repairWorkloadChartRef.value) return
|
|
|
|
+ const chart = echarts.init(repairWorkloadChartRef.value)
|
|
|
|
+ const option = {
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'axis',
|
|
|
|
+ axisPointer: {
|
|
|
|
+ type: 'shadow'
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ legend: {
|
|
|
|
+ data: repairWorkloadData.value.series.map(item => item.name)
|
|
|
|
+ },
|
|
|
|
+ grid: {
|
|
|
|
+ left: '3%',
|
|
|
|
+ right: '4%',
|
|
|
|
+ bottom: '3%',
|
|
|
|
+ containLabel: true
|
|
|
|
+ },
|
|
|
|
+ xAxis: {
|
|
|
|
+ type: 'category',
|
|
|
|
+ data: repairWorkloadData.value.xAxis
|
|
|
|
+ },
|
|
|
|
+ yAxis: {
|
|
|
|
+ type: 'value',
|
|
|
|
+ name: '进尺(米)'
|
|
|
|
+ },
|
|
|
|
+ series: repairWorkloadData.value.series.map(item => ({
|
|
|
|
+ name: item.name,
|
|
|
|
+ type: 'bar',
|
|
|
|
+ data: item.data
|
|
|
|
+ }))
|
|
|
|
+ }
|
|
|
|
+ chart.setOption(option)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const initMaterials = () => {
|
|
|
|
+
|
|
|
|
+ echarts.init(materialChartRef.value).setOption({
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'item'
|
|
|
|
+ },
|
|
|
|
+ legend: {
|
|
|
|
+ // top: '5%',
|
|
|
|
+ // right: '10%',
|
|
|
|
+ // align: 'left',
|
|
|
|
+ // orient: 'vertical',
|
|
|
|
+ // icon: 'circle'
|
|
|
|
+ orient: 'horizontal', // 水平排列图例项
|
|
|
|
+ bottom: '0%', // 放置在底部
|
|
|
|
+ icon: 'circle'
|
|
|
|
+ },
|
|
|
|
+ series: [
|
|
|
|
+ {
|
|
|
|
+ name: '',
|
|
|
|
+ type: 'pie',
|
|
|
|
+ radius: ['50%', '80%'],
|
|
|
|
+ avoidLabelOverlap: false,
|
|
|
|
+ center: ['50%', '44%'],
|
|
|
|
+ label: {
|
|
|
|
+ show: false,
|
|
|
|
+ position: 'outside'
|
|
|
|
+ },
|
|
|
|
+ emphasis: {
|
|
|
|
+ label: {
|
|
|
|
+ show: true,
|
|
|
|
+ fontSize: 15,
|
|
|
|
+ fontWeight: 'bold'
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ labelLine: {
|
|
|
|
+ show: false
|
|
|
|
+ },
|
|
|
|
+ data: materialData.value
|
|
|
|
+ }
|
|
|
|
+ ]
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/** 初始化图表 */
|
|
|
|
+const initDeviceStatusCharts = () => {
|
|
|
|
+ // 设备数量统计
|
|
|
|
+ echarts.init(statusChartRef.value).setOption({
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'item'
|
|
|
|
+ },
|
|
|
|
+ legend: {
|
|
|
|
+ orient: 'horizontal', // 水平排列图例项
|
|
|
|
+ bottom: '0%', // 放置在底部
|
|
|
|
+ icon: 'circle'
|
|
|
|
+ },
|
|
|
|
+ series: [
|
|
|
|
+ {
|
|
|
|
+ name: '',
|
|
|
|
+ type: 'pie',
|
|
|
|
+ radius: ['50%', '80%'],
|
|
|
|
+ avoidLabelOverlap: false,
|
|
|
|
+ center: ['50%', '46%'],
|
|
|
|
+ label: {
|
|
|
|
+ show: false,
|
|
|
|
+ position: 'outside'
|
|
|
|
+ },
|
|
|
|
+ emphasis: {
|
|
|
|
+ label: {
|
|
|
|
+ show: true,
|
|
|
|
+ fontSize: 15,
|
|
|
|
+ fontWeight: 'bold'
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ labelLine: {
|
|
|
|
+ show: false
|
|
|
|
+ },
|
|
|
|
+ data: typeData.value
|
|
|
|
+ }
|
|
|
|
+ ]
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/** 初始化消息统计图表 */
|
|
|
|
+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: ['空压机','增压机','提纯撬'],
|
|
|
|
+ repairs: [10, 30, 90 ]
|
|
|
|
+ })
|
|
|
|
+ }, 300)
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 初始化图表配置
|
|
|
|
+const initYwcbChart = async () => {
|
|
|
|
+ if (!chartContainer.value) return
|
|
|
|
+
|
|
|
|
+ // 获取数据
|
|
|
|
+ const { months, faults, repairs } = await fetchChartData()
|
|
|
|
+ // const months = ['空压机','增压机','提纯撬']
|
|
|
|
+ // 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: 1
|
|
|
|
+ },
|
|
|
|
+ grid: {
|
|
|
|
+ left: '3%',
|
|
|
|
+ right: '4%',
|
|
|
|
+ bottom: '1%',
|
|
|
|
+ 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: '#f69606' },
|
|
|
|
+ ])
|
|
|
|
+ },
|
|
|
|
+ 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: '18%',
|
|
|
|
+ containLabel: true
|
|
|
|
+ },
|
|
|
|
+ xAxis: {
|
|
|
|
+ type: 'value',
|
|
|
|
+ axisLabel: {
|
|
|
|
+ formatter: (value) => {
|
|
|
|
+ if (value >= 10000) return `${(value / 10000).toFixed(1)}万`
|
|
|
|
+ return value.toLocaleString()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ yAxis: {
|
|
|
|
+ type: 'category',
|
|
|
|
+ data: data.map((item) => item.category),
|
|
|
|
+ axisTick: { show: false },
|
|
|
|
+ axisLabel: {}
|
|
|
|
+ },
|
|
|
|
+ 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: '#333',
|
|
|
|
+ fontWeight: 'bold'
|
|
|
|
+ },
|
|
|
|
+ emphasis: {
|
|
|
|
+ itemStyle: {
|
|
|
|
+ shadowBlur: 10,
|
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ ]
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 初始化图表
|
|
|
|
+const initTopChart = async () => {
|
|
|
|
+ await IotStatApi.getDeviceTypeCount().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([])
|
|
|
|
+const initActiveChart = async () => {
|
|
|
|
+ if (!activeDom.value) return
|
|
|
|
+ activeData.value = await IotStatApi.getDeptCount()
|
|
|
|
+ activeInstance = echarts.init(activeDom.value)
|
|
|
|
+
|
|
|
|
+ const option = {
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'axis',
|
|
|
|
+ axisPointer: { type: 'shadow' },
|
|
|
|
+ formatter: (params) => `
|
|
|
|
+ ${params[0].name}<br/>
|
|
|
|
+ ${params[0].marker} 总人数: ${params[0].value}<br/>
|
|
|
|
+ ${params[1].marker} 活跃人数: ${params[1].value}
|
|
|
|
+ `
|
|
|
|
+ },
|
|
|
|
+ legend: {
|
|
|
|
+ data: ['总人数', '活跃人数'],
|
|
|
|
+ top: 30
|
|
|
|
+ },
|
|
|
|
+ grid: {
|
|
|
|
+ left: '3%',
|
|
|
|
+ right: '4%',
|
|
|
|
+ bottom: '3%',
|
|
|
|
+ containLabel: true
|
|
|
|
+ },
|
|
|
|
+ xAxis: {
|
|
|
|
+ type: 'category',
|
|
|
|
+ data: activeData.value.map((item) => item.department),
|
|
|
|
+ axisLabel: {
|
|
|
|
+ interval: 0,
|
|
|
|
+ rotate: 0
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ yAxis: {
|
|
|
|
+ type: 'value',
|
|
|
|
+ name: '人数',
|
|
|
|
+ splitLine: {
|
|
|
|
+ show: true,
|
|
|
|
+ lineStyle: { type: 'dashed' }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ series: [
|
|
|
|
+ {
|
|
|
|
+ name: '总人数',
|
|
|
|
+ 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: '活跃人数',
|
|
|
|
+ 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)
|
|
|
|
+ debugger
|
|
|
|
+ const option = {
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'axis',
|
|
|
|
+ axisPointer: {
|
|
|
|
+ type: 'cross',
|
|
|
|
+ label: {
|
|
|
|
+ backgroundColor: '#6a7985'
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ legend: {
|
|
|
|
+ data: orderSevenData.value.series.map((item) => item.name),
|
|
|
|
+ top: 30
|
|
|
|
+ },
|
|
|
|
+ grid: {
|
|
|
|
+ left: '3%',
|
|
|
|
+ right: '4%',
|
|
|
|
+ bottom: '3%',
|
|
|
|
+ containLabel: true
|
|
|
|
+ },
|
|
|
|
+ xAxis: {
|
|
|
|
+ type: 'category',
|
|
|
|
+ boundaryGap: false,
|
|
|
|
+ data: orderSevenData.value.xAxis,
|
|
|
|
+ axisLabel: {
|
|
|
|
+ formatter: (value) => value.split('-').join('/') // 显示为 2023/01
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ yAxis: [
|
|
|
|
+ {
|
|
|
|
+ type: 'value',
|
|
|
|
+ axisLabel: {
|
|
|
|
+ formatter: '{value}'
|
|
|
|
+ },
|
|
|
|
+ position: 'left' // 左侧 Y 轴
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ type: 'value',
|
|
|
|
+ axisLabel: {
|
|
|
|
+ formatter: '{value}'
|
|
|
|
+ },
|
|
|
|
+ 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()
|
|
|
|
+
|
|
|
|
+// 新增:初始化修井完成情况图表
|
|
|
|
+const initRepairWellChart = () => {
|
|
|
|
+ if (!repairWellChartRef.value) return
|
|
|
|
+ const option = {
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'axis',
|
|
|
|
+ axisPointer: {
|
|
|
|
+ type: 'shadow'
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ legend: {
|
|
|
|
+ data: repairWellData.value.series.map(item => item.name),
|
|
|
|
+ top: 10
|
|
|
|
+ },
|
|
|
|
+ grid: {
|
|
|
|
+ left: '3%',
|
|
|
|
+ right: '4%',
|
|
|
|
+ bottom: '3%',
|
|
|
|
+ containLabel: true
|
|
|
|
+ },
|
|
|
|
+ xAxis: {
|
|
|
|
+ type: 'category',
|
|
|
|
+ data: repairWellData.value.xAxis
|
|
|
|
+ },
|
|
|
|
+ yAxis: {
|
|
|
|
+ type: 'value',
|
|
|
|
+ name: '完成井数(口)'
|
|
|
|
+ },
|
|
|
|
+ series: repairWellData.value.series.map(item => ({
|
|
|
|
+ name: item.name,
|
|
|
|
+ type: 'bar',
|
|
|
|
+ data: item.data
|
|
|
|
+ }))
|
|
|
|
+ }
|
|
|
|
+ const chart = echarts.init(repairWellChartRef.value)
|
|
|
|
+ chart.setOption(option)
|
|
|
|
+ window.addEventListener('resize', () => chart.resize())
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 新增:初始化钻井完成情况图表
|
|
|
|
+const initDrillingWellChart = () => {
|
|
|
|
+ if (!drillingWellChartRef.value) return
|
|
|
|
+ const option = {
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'axis',
|
|
|
|
+ axisPointer: {
|
|
|
|
+ type: 'shadow'
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ legend: {
|
|
|
|
+ data: drillingWellData.value.series.map(item => item.name),
|
|
|
|
+ top: 10
|
|
|
|
+ },
|
|
|
|
+ grid: {
|
|
|
|
+ left: '3%',
|
|
|
|
+ right: '4%',
|
|
|
|
+ bottom: '3%',
|
|
|
|
+ containLabel: true
|
|
|
|
+ },
|
|
|
|
+ xAxis: {
|
|
|
|
+ type: 'category',
|
|
|
|
+ data: drillingWellData.value.xAxis
|
|
|
|
+ },
|
|
|
|
+ yAxis: {
|
|
|
|
+ type: 'value',
|
|
|
|
+ name: '完成井数(口)'
|
|
|
|
+ },
|
|
|
|
+ series: drillingWellData.value.series.map(item => ({
|
|
|
|
+ name: item.name,
|
|
|
|
+ type: 'bar',
|
|
|
|
+ data: item.data
|
|
|
|
+ }))
|
|
|
|
+ }
|
|
|
|
+ const chart = echarts.init(drillingWellChartRef.value)
|
|
|
|
+ chart.setOption(option)
|
|
|
|
+ window.addEventListener('resize', () => chart.resize())
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/** 初始化 */
|
|
|
|
+onMounted(() => {
|
|
|
|
+ getStats()
|
|
|
|
+ // initChart()
|
|
|
|
+ // initTopChart()
|
|
|
|
+ // initActiveChart()
|
|
|
|
+ // initQxChart()
|
|
|
|
+ window.addEventListener('resize', resizeQxChart)
|
|
|
|
+ // fetchTop()
|
|
|
|
+ window.addEventListener('resize', () => topInstance?.resize())
|
|
|
|
+ initRepairWellChart()
|
|
|
|
+ initDrillingWellChart()
|
|
|
|
+ initDrillingWorkloadChart()
|
|
|
|
+ initRepairWorkloadChart()
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+onBeforeUnmount(() => {
|
|
|
|
+ chartInstance?.dispose()
|
|
|
|
+ window.removeEventListener('resize', () => chartInstance?.resize())
|
|
|
|
+ topInstance?.dispose()
|
|
|
|
+ window.removeEventListener('resize', handleResize)
|
|
|
|
+ qxInstance?.dispose()
|
|
|
|
+ window.removeEventListener('resize', resizeQxChart)
|
|
|
|
+ echarts.getInstanceByDom(repairWellChartRef.value)?.dispose()
|
|
|
|
+ window.removeEventListener('resize', () => echarts.getInstanceByDom(repairWellChartRef.value)?.resize())
|
|
|
|
+ echarts.getInstanceByDom(drillingWellChartRef.value)?.dispose()
|
|
|
|
+ window.removeEventListener('resize', () => echarts.getInstanceByDom(drillingWellChartRef.value)?.resize())
|
|
|
|
+})
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style lang="scss" scoped></style>
|