123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704 |
- <template>
- <!-- 第一行:统计卡片行 -->
- <el-row :gutter="16" class="mb-4">
- <el-col :span="24">
- <el-card class="chart-card" shadow="never">
- <el-form
- class="-mb-15px"
- :model="queryParams"
- ref="queryFormRef"
- :inline="true"
- label-width="68px"
- >
- <el-form-item label="所属部门" prop="project_name">
- <el-tree-select
- v-model="queryParams.deptId"
- :data="deptList"
- :props="defaultProps"
- check-strictly
- node-key="id"
- filterable
- placeholder="请选择所在部门"
- clearable
- style="width: 180px"
- />
- </el-form-item>
- <el-form-item label="创建时间" prop="createTime">
- <el-date-picker
- v-model="queryParams.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-220px"
- />
- </el-form-item>
- <!-- <el-form-item label="填写状态" prop="project_name">
- <el-select
- v-model="queryParams.status"
- placeholder="填写状态"
- clearable
- class="!w-240px"
- >
- <el-option
- v-for="dict in getIntDictOptions(DICT_TYPE.OPERATION_FILL_ORDER_STATUS)"
- :key="dict.value"
- :label="dict.label"
- :value="dict.value"
- />
- </el-select>
- </el-form-item>-->
- <el-form-item>
- <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
- <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- </el-col>
- </el-row>
- <!-- 第二行:图表行 -->
- <el-row :gutter="16" class="mb-4">
- <el-col :span="12">
- <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>
- <el-row class="h-[220px]" style="display: flex; justify-content: space-around;">
- <el-col :span="4" class="flex flex-col items-center" @click="openFill(queryParams.deptId,4,queryParams.createTime)">
- <div ref="reportingChartRef" class="h-[160px] w-full"></div>
- <div class="text-center mt-2" >
- <span class="text-sm text-gray-600">总数</span>
- </div>
- </el-col>
- <el-col :span="4" class="flex flex-col items-center" @click="openFill(queryParams.deptId,1,queryParams.createTime)">
- <div ref="dealFinishedChartRef" class="h-[160px] w-full"></div>
- <div class="text-center mt-2">
- <span class="text-sm text-gray-600">已填写</span>
- </div>
- </el-col>
- <el-col :span="4" class="flex flex-col items-center" @click="openFill(queryParams.deptId,0,queryParams.createTime)">
- <div ref="transOrderChartRef" class="h-[160px] w-full"></div>
- <div class="text-center mt-2">
- <span class="text-sm text-gray-600">未填写</span>
- </div>
- </el-col>
- <el-col :span="4" class="flex flex-col items-center" @click="openFill(queryParams.deptId,2,queryParams.createTime)">
- <div ref="writeChartRef" class="h-[160px] w-full"></div>
- <div class="text-center mt-2">
- <span class="text-sm text-gray-600">填写中</span>
- </div>
- </el-col>
- <el-col :span="4" class="flex flex-col items-center" @click="openFill(queryParams.deptId,3,queryParams.createTime)">
- <div ref="ignoreChartRef" class="h-[160px] w-full"></div>
- <div class="text-center mt-2">
- <span class="text-sm text-gray-600" >忽略</span>
- </div>
- </el-col>
- </el-row>
- </el-card>
- </el-col>
- <el-col :span="12">
- <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>
- <el-row class="h-[220px]" style="display: flex; justify-content: space-around;">
- <el-col :span="4" class="flex flex-col items-center" @click="openForm(queryParams.deptId,2,queryParams.createTime)">
- <div ref="reportingChartRef1" class="h-[160px] w-full"></div>
- <div class="text-center mt-2" >
- <span class="text-sm text-gray-600">总数</span>
- </div>
- </el-col>
- <el-col :span="4" class="flex flex-col items-center" @click="openForm(queryParams.deptId,1,queryParams.createTime)">
- <div ref="dealFinishedChartRef1" class="h-[160px] w-full"></div>
- <div class="text-center mt-2">
- <span class="text-sm text-gray-600">已填写</span>
- </div>
- </el-col>
- <el-col :span="4" class="flex flex-col items-center" @click="openForm(queryParams.deptId,0,queryParams.createTime)">
- <div ref="transOrderChartRef1" class="h-[160px] w-full"></div>
- <div class="text-center mt-2">
- <span class="text-sm text-gray-600">未填写</span>
- </div>
- </el-col>
- <el-col :span="4" class="flex flex-col items-center" @click="openForm(queryParams.deptId,3,queryParams.createTime)">
- <div ref="ignoreChartRef1" class="h-[160px] w-full"></div>
- <div class="text-center mt-2">
- <span class="text-sm text-gray-600">忽略</span>
- </div>
- </el-col>
- </el-row>
- </el-card>
- </el-col>
- </el-row>
- <!-- 第三行:消息统计行 -->
- <el-row>
- <el-col :span="24">
- <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="chartContainer" class="h-[300px]"></div>
- </el-card>
- </el-col>
- </el-row>
- </template>
- <script setup lang="ts" name="Index">
- import * as echarts from 'echarts/core'
- import { BarChart } from 'echarts/charts'; // 显式导入柱状图模块
- import {
- GridComponent,
- LegendComponent,
- TitleComponent,
- ToolboxComponent,
- TooltipComponent
- } from 'echarts/components'
- import { GaugeChart, LineChart, PieChart } from 'echarts/charts'
- import { LabelLayout, UniversalTransition } from 'echarts/features'
- import { CanvasRenderer } from 'echarts/renderers'
- import {
- IotStatisticsDeviceMessageSummaryRespVO,
- IotStatisticsSummaryRespVO
- } from '@/api/iot/statistics'
- import { formatDate } from '@/utils/formatTime'
- import { IotStatApi } from '@/api/pms/stat'
- import { ref, reactive, onMounted, onUnmounted } from "vue";
- import { defaultProps, handleTree } from "@/utils/tree";
- import * as DeptApi from "@/api/system/dept";
- import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
- import {useUserStore} from "@/store/modules/user";
- // 导入部门数据类型
- import { DeptTreeItem } from '@/api/system/dept'
- import StatisticsForm from './StatisticsForm.vue'
- const { push } = useRouter()
- // 初始化echarts
- echarts.use([
- TooltipComponent,
- LegendComponent,
- PieChart,
- CanvasRenderer,
- LabelLayout,
- TitleComponent,
- ToolboxComponent,
- GridComponent,
- LineChart,
- UniversalTransition,
- GaugeChart,
- BarChart
- ])
- const deptList = ref<DeptTreeItem[]>([]) // 树形结构部门列表
- const deptDataList = ref<DeptDataItem[]>([]) // 部门数据列表
- // 定义部门数据类型
- interface DeptDataItem {
- deptId: number;
- name: string;
- totalCount: number;
- filledCount: number;
- unfilledCount: number;
- fillingCount: number;
- }
- /** IoT 首页 */
- defineOptions({ name: 'iotOpeationSta' })
- 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(), // 设置默认结束时间为当前时间
- createTime: [],
- deptId: null, // 选中的部门ID
- status: null // 填写状态
- })
- const handleQuery = () => {
- // 重新获取数据
- getStats()
- getDevStats()
- initChart()
- }
- const formRef = ref()
- const openForm = (deptId?:string,isFill?:string,createTime?:[]) => {
- push({ name: 'FillOrderInfo2',params:{deptId,isFill,createTime}})
- }
- const openFill = (deptId?:string,orderStatus?:string,createTime?:[]) => {
- push({
- name: 'IotOpeationFill1',
- query: { // 使用query传递参数,而非params
- deptId,
- orderStatus,
- // 数组可以直接传递,会自动序列化
- createTime: createTime ? createTime.join(',') : undefined
- // 建议将数组转为字符串,避免路由参数格式问题
- }
- })
- }
- const resetQuery = () => {
- // 重置查询参数
- queryParams.startTime = Date.now() - 7 * 24 * 60 * 60 * 1000
- queryParams.endTime = Date.now()
- queryParams.deptId = useUserStore().getUser.deptId;
- queryParams.createTime = null
- // 重新获取数据
- getStats()
- initChart()
- }
- //运行记录工单统计
- const reportingChartRef = ref() // 在线设备统计的图表
- const dealFinishedChartRef = ref() // 离线设备统计的图表
- const transOrderChartRef = ref() // 待激活设备统计的图表
- const writeChartRef = ref() // 待填写图表
- const ignoreChartRef = ref() // 上下行消息量统计的图表
- //运行记录设备统计
- const reportingChartRef1 = ref() // 在线设备统计的图表
- const dealFinishedChartRef1 = ref() // 离线设备统计的图表
- const transOrderChartRef1 = ref() // 待激活设备统计的图表
- const ignoreChartRef1 = ref() // 上下行消息量统计的图表
- // 基础统计数据
- 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 day = ref({
- failureDay: undefined,
- maintainDay: undefined
- })
- const week = ref({
- failureWeek: undefined,
- maintainWeek: undefined
- })
- const month = ref({
- failureMonth: undefined,
- maintainMonth: undefined
- })
- const total = ref({
- failureTotal: undefined,
- maintainTotal: undefined
- })
- const status = ref<IotStatusItem[]>([])
- const status1 = ref<IotStatusItem[]>([])
- // 定义状态项接口
- interface IotStatusItem {
- totalCount: number
- filledCount: number
- unfilledCount: number
- fillingCount: number
- }
- // 消息统计数据
- const messageStats = ref<IotStatisticsDeviceMessageSummaryRespVO>({
- upstreamCounts: {},
- downstreamCounts: {}
- })
- /** 处理快捷时间范围选择 */
- const handleTimeRangeChange = (timeRange: string) => {
- const now = Date.now()
- let startTime: number
- switch (timeRange) {
- case '1h':
- startTime = now - 60 * 60 * 1000
- break
- case '24h':
- startTime = now - 24 * 60 * 60 * 1000
- break
- case '7d':
- startTime = now - 7 * 24 * 60 * 60 * 1000
- break
- default:
- return
- }
- // 清空日期选择器
- dateRange.value = null
- // 更新查询参数
- queryParams.startTime = startTime
- queryParams.endTime = now
- // 重新获取数据
- getStats()
- initChart()
- }
- /** 处理自定义日期范围选择 */
- const handleDateRangeChange = (value: [Date, Date] | null) => {
- if (value) {
- // 清空快捷选项
- timeRange.value = ''
- // 更新查询参数
- queryParams.startTime = value[0].getTime()
- queryParams.endTime = value[1].getTime()
- // 重新获取数据
- getStats()
- initChart()
- }
- }
- /** 获取统计数据 */
- const getStats = async () => {
- // 获取基础统计数据
- // 获取部门统计数据
- IotStatApi.getDeptStatistics(queryParams).then((res) => {
- deptDataList.value = res.deptCountList || [];
- status.value = res.totalList || [];
- initChart()
- initCharts()
- })
- }
- const getDevStats = async () => {
- // 获取基础统计数据
- // 获取部门统计数据
- IotStatApi.getDevSta(queryParams).then((res) => {
- status1.value = res.totalList || [];
- initChart()
- initCharts()
- })
- }
- /** 初始化图表 */
- const initCharts = () => {
- // 使用数组的第一个元素(如果存在)
- const firstStatus = status.value[0] || {}
- const firstStatus1 = status1.value[0] || {}
- // 上报中
- initGaugeChart(
- reportingChartRef.value,
- firstStatus.totalCount === undefined ? 0 : firstStatus.totalCount,
- '#0d9'
- )
- initGaugeChart(
- reportingChartRef1.value,
- firstStatus1.totalCount === undefined ? 0 : firstStatus1.totalCount,
- '#0d9'
- )
- // 处理完成
- initGaugeChart(
- dealFinishedChartRef.value,
- firstStatus.filledCount === undefined ? 0 : firstStatus.filledCount,
- '#05b'
- )
- initGaugeChart(
- ignoreChartRef.value,
- firstStatus.ignoreCount === undefined ? 0 : firstStatus.ignoreCount,
- 'purple'
- )
- initGaugeChart(
- ignoreChartRef1.value,
- firstStatus1.ignoreCount === undefined ? 0 : firstStatus1.ignoreCount,
- 'purple'
- )
- initGaugeChart(
- dealFinishedChartRef1.value,
- firstStatus1.filledCount === undefined ? 0 : firstStatus1.filledCount,
- '#05b'
- )
- // 转工单
- initGaugeChart(
- transOrderChartRef.value,
- firstStatus.unfilledCount === undefined ? 0 : firstStatus.unfilledCount,
- '#f50'
- )
- initGaugeChart(
- transOrderChartRef1.value,
- firstStatus1.unfilledCount === undefined ? 0 : firstStatus1.unfilledCount,
- '#f50'
- )
- // 待填写
- initGaugeChart(
- writeChartRef.value,
- firstStatus.fillingCount === undefined ? 0 : firstStatus.fillingCount,
- '#05b'
- )
- }
- /** 初始化仪表盘图表 */
- 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 chartContainer = ref(null)
- let chartInstance = null
- // 初始化部门统计图表
- const initChart = () => {
- if (!chartContainer.value) return
- // 准备数据
- const deptNames = deptDataList.value.map(item => item.name)
- const totalCounts = deptDataList.value.map(item => item.totalCount)
- const filledCounts = deptDataList.value.map(item => item.filledCount)
- const unfilledCounts = deptDataList.value.map(item => item.unfilledCount)
- const fillingCounts = deptDataList.value.map(item => item.fillingCount)
- // ECharts配置
- const option = {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- },
- formatter: (params) => {
- let result = `<div class="font-bold">${params[0].axisValue}</div>`
- params.forEach(param => {
- result += `<div>${param.marker} ${param.seriesName}: ${param.value}</div>`
- })
- return result
- }
- },
- legend: {
- data: ['总数', '已填写', '未填写', '填写中'],
- top: 25
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- data: deptNames,
- axisLabel: {
- rotate: 45,
- margin: 15,
- fontSize: 10
- }
- },
- yAxis: {
- type: 'value',
- axisLabel: {
- formatter: (value) => Math.floor(value).toString()
- }
- },
- series: [
- {
- name: '总数',
- type: 'bar',
- barGap: 0,
- itemStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: '#188df0' },
- { offset: 1, color: '#188df0' }
- ])
- },
- emphasis: {
- focus: 'series'
- },
- data: totalCounts
- },
- {
- name: '已填写',
- type: 'bar',
- itemStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: '#d3a137' },
- { offset: 1, color: '#d3a137' }
- ])
- },
- emphasis: {
- focus: 'series'
- },
- data: filledCounts
- },
- {
- name: '未填写',
- type: 'bar',
- itemStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: 'green' },
- { offset: 1, color: 'green' }
- ])
- },
- emphasis: {
- focus: 'series'
- },
- data: unfilledCounts
- },
- {
- name: '填写中',
- type: 'bar',
- itemStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: 'red' },
- { offset: 1, color: 'red' }
- ])
- },
- emphasis: {
- focus: 'series'
- },
- data: fillingCounts
- }
- ]
- }
- // 初始化图表
- chartInstance = echarts.init(chartContainer.value)
- chartInstance.setOption(option)
- // 窗口缩放监听
- window.addEventListener('resize', handleResize)
- handleResize()
- }
- // 自适应调整
- const handleResize = () => {
- chartInstance?.resize()
- }
- const router = useRouter()
- const route = useRoute()
- // 监听路由变化
- const removeBeforeEach = router.beforeEach((to, from, next) => {
- // 如果是从FillOrderInfo1页面返回
- if (from.name === 'FillOrderInfo1' && to.name === route.name) {
- // 恢复查询参数
- Object.assign(queryParams, initialQueryParams)
- // 阻止刷新,使用replace而不是push
- next({ ...to, replace: true })
- } else {
- next()
- }
- })
- /** 初始化 */
- onMounted(async () => {
- queryParams.deptId = useUserStore().getUser.deptId;
- // 计算近一周时间
- const end = new Date();
- const start = new Date();
- start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000);
- // 格式化日期为后端需要的格式
- 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}`;
- };
- queryParams.createTime = [formatDate(start), formatDate(end)];
- getStats()
- getDevStats();
- deptList.value = handleTree(await DeptApi.getSimpleDeptList())
- })
- </script>
- <style lang="scss" scoped>
- .chart-card {
- background-color: white;
- border-radius: 8px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
- padding: 16px;
- }
- // 新增样式,隐藏滚动条但保留功能
- ::-webkit-scrollbar {
- display: none;
- }
- :host {
- overflow: hidden;
- }
- // 确保页面铺满屏幕并不出现滚动条
- html, body {
- height: 100%;
- margin: 0;
- padding: 0;
- overflow: hidden;
- }
- // 适配图表容器高度
- .el-row {
- max-height: calc(100vh - 32px); // 减去页面padding
- overflow: auto;
- -ms-overflow-style: none; // 隐藏IE滚动条
- scrollbar-width: none; // 隐藏Firefox滚动条
- }
- </style>
|