Pārlūkot izejas kodu

Merge branch 'device_book' of shuzhihua/pms-iot-vue into master

yanghao 9 stundas atpakaļ
vecāks
revīzija
54c8393c2e

+ 1 - 1
.env.local

@@ -4,7 +4,7 @@ NODE_ENV=development
 VITE_DEV=true
 
 # 请求路径  http://192.168.188.149:48080  https://iot.deepoil.cc
-VITE_BASE_URL='https://iot.deepoil.cc'
+VITE_BASE_URL='http://192.168.188.149:48080'
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
 VITE_UPLOAD_TYPE=server

+ 17 - 0
src/api/pms/device/index.ts

@@ -161,6 +161,23 @@ export const IotDeviceApi = {
     return await request.get({ url: `/rq/iot-device/dept/${id}` })
   },
 
+  // 设备总数量
+  getIotDeviceCount: async (params: any) => {
+    return await request.get({ url: `/rq/report/device/count`, params })
+  },
+
+  // 设备状态
+  getIotDeviceStatus: async (params: any) => {
+    return await request.get({ url: `/rq/report/device/status`, params })
+  },
+  // 分类top
+  getIotDeviceClassify: async (params: any) => {
+    return await request.get({ url: `/rq/report/device/type`, params })
+  },
+  // 列表
+  getIotDeviceList: async (params: any) => {
+    return await request.get({ url: `/rq/report/device/page`, params })
+  },
   // 查看详情时获取成套设备列表
   getIotDeviceSets: async (params: any) => {
     return await request.get({ url: `/rq/iot-device-group-detail/page`, params })

+ 20 - 2
src/api/pms/inspect/order/index.ts

@@ -9,7 +9,7 @@ export interface IotInspectOrderVO {
   remark: string // 备注
   deptId: number // 部门id
   deviceIds: string // 设备id
-  details:[]
+  details: []
 }
 
 // 巡检工单 API
@@ -30,7 +30,7 @@ export const IotInspectOrderApi = {
     return await request.get({ url: `/rq/iot-inspect-order/get?id=` + id })
   },
   orderReason: async (params: {}): Promise<any> => {
-    return await request.post({ url: `/rq/iot-inspect-order/ignore`,params })
+    return await request.post({ url: `/rq/iot-inspect-order/ignore`, params })
   },
   getIotInspectOrderDetail: async (id: number) => {
     return await request.get({ url: `/rq/iot-inspect-order/get/details?id=` + id })
@@ -57,5 +57,23 @@ export const IotInspectOrderApi = {
   //填写巡检工单
   writeIotInspectOrder: async (data: any, orderId) => {
     return await request.post({ url: `/rq/iot-inspect-order/write/` + orderId, data })
+  },
+
+  // 报表统计相关接口
+  // 巡检工单列表
+  getIotInspectOrderList: async (params: any) => {
+    return await request.get({ url: `/rq/iot-inspect-order/report/stat`, params })
+  },
+  // 异常设备数量
+  getIotInspectOrderExceptionDeviceCount: async (params: any) => {
+    return await request.get({ url: `/rq/stat/inspect/exception/device`, params })
+  },
+  // 巡检异常点数量
+  getIotInspectOrderExceptionPointCount: async (params: any) => {
+    return await request.get({ url: `/rq/iot-inspect-order-detail/report/status`, params })
+  },
+  // 设备状态接口
+  getInspectItemStatus: async (params: any) => {
+    return await request.get({ url: `/rq/stat/report/inspect/status`, params })
   }
 }

+ 147 - 48
src/views/pms/device/index.vue

@@ -2,7 +2,7 @@
   <el-row :gutter="20">
     <!-- 左侧部门树 -->
     <el-col :span="4" :xs="24">
-<!--      <div><Icon icon="ep:edit" @click="shou(treeShow)"/> </div>-->
+      <!--      <div><Icon icon="ep:edit" @click="shou(treeShow)"/> </div>-->
       <ContentWrap class="h-1/1" v-if="treeShow">
         <DeptTree @node-click="handleDeptNodeClick" />
       </ContentWrap>
@@ -17,7 +17,11 @@
           :inline="true"
           label-width="68px"
         >
-          <el-form-item :label="t('iotDevice.yfCode')" prop="yfDeviceCode" style="margin-left: 20px">
+          <el-form-item
+            :label="t('iotDevice.yfCode')"
+            prop="yfDeviceCode"
+            style="margin-left: 20px"
+          >
             <el-input
               v-model="queryParams.yfDeviceCode"
               :placeholder="t('iotDevice.yfCodeHolder')"
@@ -54,7 +58,12 @@
             />
           </el-form-item>
 
-          <el-form-item v-show="ifShow" :label="t('iotDevice.status')" label-width="85px" prop="deviceStatus">
+          <el-form-item
+            v-show="ifShow"
+            :label="t('iotDevice.status')"
+            label-width="85px"
+            prop="deviceStatus"
+          >
             <el-select
               v-model="queryParams.deviceStatus"
               :placeholder="t('iotDevice.statusHolder')"
@@ -85,7 +94,12 @@
               />
             </el-select>
           </el-form-item>
-          <el-form-item v-show="ifShow" :label="t('deviceForm.category')" prop="assetClass" style="width: 15vw" >
+          <el-form-item
+            v-show="ifShow"
+            :label="t('deviceForm.category')"
+            prop="assetClass"
+            style="width: 15vw"
+          >
             <el-tree-select
               v-model="queryParams.assetClass"
               :data="productClassifyList"
@@ -99,17 +113,16 @@
 
           <el-form-item>
             <el-button v-if="!ifShow" @click="moreQuery(true)" type="warning"
-              ><Icon icon="ep:search" class="mr-5px" />
-              {{ t('iotDevice.moreSearch') }}</el-button
+              ><Icon icon="ep:search" class="mr-5px" /> {{ t('iotDevice.moreSearch') }}</el-button
             >
             <el-button v-if="ifShow" @click="moreQuery(false)" type="danger"
-              ><Icon icon="ep:search" class="mr-5px" />  {{ t('iotDevice.closeSearch') }}</el-button
+              ><Icon icon="ep:search" class="mr-5px" /> {{ t('iotDevice.closeSearch') }}</el-button
             >
             <el-button @click="handleQuery"
-              ><Icon icon="ep:search" class="mr-5px" />{{t('operationFill.search')}}</el-button
+              ><Icon icon="ep:search" class="mr-5px" />{{ t('operationFill.search') }}</el-button
             >
             <el-button @click="resetQuery"
-              ><Icon icon="ep:refresh" class="mr-5px" /> {{t('operationFill.reset')}}</el-button
+              ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('operationFill.reset') }}</el-button
             >
             <el-button
               type="primary"
@@ -117,7 +130,7 @@
               @click="openForm('create', undefined, queryParams.deptId)"
               v-hasPermi="['rq:iot-device:create']"
             >
-              <Icon icon="ep:plus" class="mr-5px" /> {{t('operationFill.add')}}
+              <Icon icon="ep:plus" class="mr-5px" /> {{ t('operationFill.add') }}
             </el-button>
             <el-button
               type="success"
@@ -134,40 +147,120 @@
 
       <!-- 列表 -->
       <ContentWrap>
-        <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @sort-change="handleSortChange">
+        <el-table
+          v-loading="loading"
+          :data="list"
+          :stripe="true"
+          :show-overflow-tooltip="true"
+          height="65vh"
+          @sort-change="handleSortChange"
+        >
           <el-table-column :label="t('iotDevice.serial')" width="70" align="center">
             <template #default="scope">
               {{ scope.$index + 1 }}
             </template>
           </el-table-column>
-          <el-table-column :label="t('iotDevice.yfCode')" sortable align="center" prop="yfDeviceCode" min-width="150"/>
-          <el-table-column :label="t('iotDevice.code')" sortable align="center" prop="deviceCode" min-width="150"/>
-          <el-table-column :label="t('iotDevice.name')" sortable align="center" prop="deviceName" min-width="250">
+          <el-table-column
+            :label="t('iotDevice.yfCode')"
+            sortable
+            align="center"
+            prop="yfDeviceCode"
+            width="150"
+          />
+          <el-table-column
+            :label="t('iotDevice.code')"
+            sortable
+            align="center"
+            prop="deviceCode"
+            width="150"
+          />
+          <el-table-column
+            :label="t('iotDevice.name')"
+            sortable
+            align="center"
+            prop="deviceName"
+            min-width="250"
+          >
             <template #default="scope">
               <el-link :underline="false" type="primary" @click="handleDetail(scope.row.id)">
                 {{ scope.row.deviceName }}
               </el-link>
             </template>
           </el-table-column>
-          <el-table-column :label="t('iotDevice.dept')" align="center" prop="deptName" min-width="150"/>
-          <el-table-column :label="t('iotDevice.status')" align="center" prop="deviceStatus" min-width="90">
+          <el-table-column
+            :label="t('iotDevice.dept')"
+            align="center"
+            prop="deptName"
+            min-width="150"
+          />
+          <el-table-column
+            :label="t('iotDevice.status')"
+            align="center"
+            prop="deviceStatus"
+            min-width="90"
+          >
             <template #default="scope">
               <dict-tag :type="DICT_TYPE.PMS_DEVICE_STATUS" :value="scope.row.deviceStatus" />
             </template>
           </el-table-column>
-          <el-table-column :label="t('iotDevice.assets')" align="center" prop="assetProperty" min-width="110">
+          <el-table-column
+            :label="t('iotDevice.assets')"
+            align="center"
+            prop="assetProperty"
+            min-width="110"
+          >
             <template #default="scope">
               <dict-tag :type="DICT_TYPE.PMS_ASSET_PROPERTY" :value="scope.row.assetProperty" />
             </template>
           </el-table-column>
-          <el-table-column :label="t('iotDevice.assetClass')" align="center" prop="assetClassName" min-width="170"/>
-          <el-table-column :label="t('deviceForm.mfg')" align="center" prop="manufacturer"  min-width="200"/>
-          <el-table-column :label="t('deviceForm.brand')" align="center" prop="brandName" min-width="150"/>
-          <el-table-column :label="t('deviceForm.model')" align="center" prop="model" min-width="170"/>
-          <el-table-column :label="t('devicePerson.rp')" align="center" prop="chargeName" min-width="170"/>
-          <el-table-column :label="t('deviceForm.useProject')" align="center" prop="useProject" min-width="170"/>
-          <el-table-column :label="t('deviceForm.assetOwner')" align="center" prop="assetOwnership" min-width="170"/>
-          <el-table-column :label="t('operationFill.operation')" align="center" min-width="180px" fixed="right">
+          <el-table-column
+            :label="t('iotDevice.assetClass')"
+            align="center"
+            prop="assetClassName"
+            min-width="170"
+          />
+          <el-table-column
+            :label="t('deviceForm.mfg')"
+            align="center"
+            prop="manufacturer"
+            min-width="200"
+          />
+          <el-table-column
+            :label="t('deviceForm.brand')"
+            align="center"
+            prop="brandName"
+            min-width="150"
+          />
+          <el-table-column
+            :label="t('deviceForm.model')"
+            align="center"
+            prop="model"
+            min-width="170"
+          />
+          <el-table-column
+            :label="t('devicePerson.rp')"
+            align="center"
+            prop="chargeName"
+            min-width="170"
+          />
+          <el-table-column
+            :label="t('deviceForm.useProject')"
+            align="center"
+            prop="useProject"
+            min-width="170"
+          />
+          <el-table-column
+            :label="t('deviceForm.assetOwner')"
+            align="center"
+            prop="assetOwnership"
+            min-width="170"
+          />
+          <el-table-column
+            :label="t('operationFill.operation')"
+            align="center"
+            min-width="180px"
+            fixed="right"
+          >
             <template #default="scope">
               <el-button
                 link
@@ -175,7 +268,7 @@
                 @click="openForm('update', scope.row.id)"
                 v-hasPermi="['rq:iot-device:update']"
               >
-                {{t('iotDevice.update')}}
+                {{ t('iotDevice.update') }}
               </el-button>
               <el-button
                 link
@@ -183,11 +276,11 @@
                 @click="handleDelete(scope.row.id)"
                 v-hasPermi="['rq:iot-device:delete']"
               >
-                {{t('iotDevice.delete')}}
+                {{ t('iotDevice.delete') }}
               </el-button>
-<!--              <el-button link type="warning" @click="handleUpload(scope.row.id)">-->
-<!--                {{t('iotDevice.upload')}}-->
-<!--              </el-button>-->
+              <!--              <el-button link type="warning" @click="handleUpload(scope.row.id)">-->
+              <!--                {{t('iotDevice.upload')}}-->
+              <!--              </el-button>-->
             </template>
           </el-table-column>
         </el-table>
@@ -212,10 +305,10 @@ import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import DeptTree from '@/views/system/user/DeptTree.vue'
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
-import {buildSortingField} from "@/utils";
-import {defaultProps, handleTree} from "@/utils/tree";
-import * as ProductClassifyApi from "@/api/pms/productclassify";
-import { useRefreshStore } from '@/store/modules/pms/refreshStore';
+import { buildSortingField } from '@/utils'
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as ProductClassifyApi from '@/api/pms/productclassify'
+import { useRefreshStore } from '@/store/modules/pms/refreshStore'
 
 /** 设备台账 列表 */
 defineOptions({ name: 'IotDevicePms' })
@@ -224,7 +317,7 @@ const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const { push } = useRouter() // 路由跳转
 
-const refreshStore = useRefreshStore();
+const refreshStore = useRefreshStore()
 const loading = ref(true) // 列表的加载中
 const ifShow = ref(false)
 const isDetail = ref(false) // 是否查看详情
@@ -265,13 +358,13 @@ const queryParams = reactive({
   creator: undefined,
   sortingFields: [],
   assetClass: undefined,
-  yfDeviceCode: undefined,
+  yfDeviceCode: undefined
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 const contentSpan = ref(20)
 const treeShow = ref(true)
-const shou = (tree) =>{
+const shou = (tree) => {
   treeShow.value = !tree
   if (tree) {
     contentSpan.value = 20
@@ -285,7 +378,7 @@ const handleSortChange = (params: any) => {
   queryParams.sortingFields = []
   queryParams.sortingFields = [buildSortingField(params)]
   getList()
-};
+}
 
 /** 查询列表 */
 const getList = async () => {
@@ -321,17 +414,17 @@ const resetQuery = () => {
 
 /** 添加/修改操作 */
 const formRef = ref()
-const openForm = (type: string, id?: number,deptId?:number) => {
+const openForm = (type: string, id?: number, deptId?: number) => {
   //修改
   if (typeof id === 'number') {
-    push({ name: 'DeviceDetailEdit', params: { type,id }, query:{source:'devicerouter'} })
+    push({ name: 'DeviceDetailEdit', params: { type, id }, query: { source: 'devicerouter' } })
     return
   }
   // 新增
   if (deptId) {
-    push({ name: 'DeviceDetailAdd', params: {type,deptId}, query:{source:'devicerouter'} })
-  }else {
-    push({ name: 'DeviceDetailAddd', params: {} ,query:{source:'devicerouter'}})
+    push({ name: 'DeviceDetailAdd', params: { type, deptId }, query: { source: 'devicerouter' } })
+  } else {
+    push({ name: 'DeviceDetailAddd', params: {}, query: { source: 'devicerouter' } })
   }
 }
 
@@ -378,12 +471,18 @@ onMounted(async () => {
   )
   const sort = {
     field: 'sortColumn',
-    order: 'asc',
+    order: 'asc'
   }
-  queryParams.sortingFields.push(sort);
+  queryParams.sortingFields.push(sort)
   await getList()
-  refreshStore.registerCallback('devicerouter', getList);
-
+  refreshStore.registerCallback('devicerouter', getList)
 })
 </script>
-<style scoped></style>
+<style scoped scss>
+::v-deep .el-table__header-wrapper {
+  position: sticky !important;
+  width: 100%;
+  top: 0px;
+  z-index: 2000;
+}
+</style>

+ 29 - 21
src/views/pms/inspect/order/detail/IotInspectOrderItemStat.vue

@@ -17,15 +17,15 @@
           class="!w-200px"
         />
       </el-form-item>
-<!--      <el-form-item :label="t('iotDevice.name')" prop="deviceName">-->
-<!--        <el-input-->
-<!--          v-model="queryParams.deviceName"-->
-<!--          :placeholder="t('iotDevice.nameHolder')"-->
-<!--          clearable-->
-<!--          @keyup.enter="handleQuery"-->
-<!--          class="!w-200px"-->
-<!--        />-->
-<!--      </el-form-item>-->
+      <!--      <el-form-item :label="t('iotDevice.name')" prop="deviceName">-->
+      <!--        <el-input-->
+      <!--          v-model="queryParams.deviceName"-->
+      <!--          :placeholder="t('iotDevice.nameHolder')"-->
+      <!--          clearable-->
+      <!--          @keyup.enter="handleQuery"-->
+      <!--          class="!w-200px"-->
+      <!--        />-->
+      <!--      </el-form-item>-->
       <el-form-item :label="t('bomList.name')" prop="orderName">
         <el-input
           v-model="queryParams.orderName"
@@ -60,21 +60,21 @@
       <el-table-column :label="t('monitor.deviceName')" align="center" prop="deviceName" />
       <el-table-column :label="t('operationFill.duty')" align="center" prop="charge" />
       <el-table-column :label="t('inspect.InspectionItems')" align="center" prop="item" />
-      <el-table-column :label="t('inspect.isException')" align="center" prop="ifNormal" >
+      <el-table-column :label="t('inspect.isException')" align="center" prop="ifNormal">
         <template #default="scope">
           <span v-if="scope.row.ifNormal" style="color: dodgerblue">正常</span>
-          <span v-else-if="scope.row.ifNormal===null" style="color: #101010">待填写</span>
+          <span v-else-if="scope.row.ifNormal === null" style="color: #101010">待填写</span>
           <span v-else-if="!scope.row.ifNormal" style="color: orangered">异常</span>
         </template>
       </el-table-column>
       <el-table-column :label="t('inspect.exceptionDes')" align="center" prop="description" />
 
-<!--      <el-table-column-->
-<!--        :label="t('common.createTime')"-->
-<!--        align="center"-->
-<!--        prop="createTime"-->
-<!--        width="180px"-->
-<!--      />-->
+      <!--      <el-table-column-->
+      <!--        :label="t('common.createTime')"-->
+      <!--        align="center"-->
+      <!--        prop="createTime"-->
+      <!--        width="180px"-->
+      <!--      />-->
     </el-table>
     <!-- 分页 -->
     <Pagination
@@ -89,13 +89,18 @@
 </template>
 
 <script setup lang="ts">
-import {IotInspectItemVO, IotInspectOrderDetailApi} from '@/api/pms/inspect/order/detail'
-import IotInspectOrderDetail from "@/views/pms/inspect/order/detail/index.vue";
-import {DICT_TYPE} from "@/utils/dict";
+import { IotInspectItemVO, IotInspectOrderDetailApi } from '@/api/pms/inspect/order/detail'
+import IotInspectOrderDetail from '@/views/pms/inspect/order/detail/index.vue'
+import { DICT_TYPE } from '@/utils/dict'
+import { useRoute } from 'vue-router'
 
 /** 巡检项 列表 */
 defineOptions({ name: 'IotInspectItemStat' })
 
+const route = useRoute()
+
+console.log('>>>>>>>>>>>>>>>>>>>>>>>>', route.query)
+
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const { params } = useRoute()
@@ -122,6 +127,10 @@ const exportLoading = ref(false) // 导出的加载中
 const getList = async () => {
   loading.value = true
   try {
+    if (route.query && route.query.orderName) {
+      queryParams.orderName = route.query.orderName
+      queryParams.status = '异常'
+    }
     const data = await IotInspectOrderDetailApi.getIotInspectItemStatusPage(queryParams)
     list.value = data.list
     total.value = data.total
@@ -144,7 +153,6 @@ const resetQuery = () => {
 
 /** 初始化 **/
 onMounted(async () => {
-  debugger
   if (params.status) {
     queryParams.status = params.status
   }

+ 121 - 67
src/views/pms/inspect/order/index.vue

@@ -24,15 +24,15 @@
               class="!w-240px"
             />
           </el-form-item>
-<!--          <el-form-item label="工单编码" prop="inspectOrderCode">-->
-<!--            <el-input-->
-<!--              v-model="queryParams.inspectOrderCode"-->
-<!--              placeholder="请输入工单编码"-->
-<!--              clearable-->
-<!--              @keyup.enter="handleQuery"-->
-<!--              class="!w-240px"-->
-<!--            />-->
-<!--          </el-form-item>-->
+          <!--          <el-form-item label="工单编码" prop="inspectOrderCode">-->
+          <!--            <el-input-->
+          <!--              v-model="queryParams.inspectOrderCode"-->
+          <!--              placeholder="请输入工单编码"-->
+          <!--              clearable-->
+          <!--              @keyup.enter="handleQuery"-->
+          <!--              class="!w-240px"-->
+          <!--            />-->
+          <!--          </el-form-item>-->
           <el-form-item :label="t('operationFill.status')" prop="status">
             <el-select
               v-model="queryParams.status"
@@ -61,8 +61,7 @@
           </el-form-item>
           <el-form-item>
             <el-button @click="handleQuery"
-              ><Icon icon="ep:search" class="mr-5px" />
-              {{ t('operationFill.search') }}</el-button
+              ><Icon icon="ep:search" class="mr-5px" /> {{ t('operationFill.search') }}</el-button
             >
             <el-button @click="resetQuery"
               ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('operationFill.reset') }}</el-button
@@ -90,37 +89,78 @@
 
       <!-- 列表 -->
       <ContentWrap>
-        <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+        <el-table
+          v-loading="loading"
+          :data="list"
+          :stripe="true"
+          height="65vh"
+          :show-overflow-tooltip="true"
+        >
           <el-table-column :label="t('iotDevice.serial')" width="70" align="center">
             <template #default="scope">
               {{ scope.$index + 1 }}
             </template>
           </el-table-column>
-          <el-table-column :label="t('bomList.name')" align="center" prop="inspectOrderTitle" min-width="300" />
-<!--          <el-table-column label="工单编码" align="center" prop="inspectOrderCode" />-->
-          <el-table-column :label="t('route.orderType')" align="center" prop="type" min-width="90"/>
-          <el-table-column :label="t('operationFill.status')" align="center" prop="status" min-width="95">
+          <el-table-column
+            :label="t('bomList.name')"
+            align="center"
+            prop="inspectOrderTitle"
+            min-width="300"
+          />
+          <!--          <el-table-column label="工单编码" align="center" prop="inspectOrderCode" />-->
+          <el-table-column
+            :label="t('route.orderType')"
+            align="center"
+            prop="type"
+            min-width="90"
+          />
+          <el-table-column
+            :label="t('operationFill.status')"
+            align="center"
+            prop="status"
+            min-width="95"
+          >
             <template #default="scope">
               <dict-tag :type="DICT_TYPE.PMS_INSPECT_ORDER_STATUS" :value="scope.row.status" />
             </template>
           </el-table-column>
-          <el-table-column :label="t('iotMaintain.PersonInCharge')" align="center" prop="chargeName" min-width="110"/>
-          <el-table-column :label="t('inspect.deviceCount')" align="center" prop="deviceCount" min-width="100">
+          <el-table-column
+            :label="t('iotMaintain.PersonInCharge')"
+            align="center"
+            prop="chargeName"
+            min-width="110"
+          />
+          <el-table-column
+            :label="t('inspect.deviceCount')"
+            align="center"
+            prop="deviceCount"
+            min-width="100"
+          >
             <template #default="scope">
-              <el-tag  type="info"> {{scope.row.deviceCount}}</el-tag>
+              <el-tag type="info"> {{ scope.row.deviceCount }}</el-tag>
             </template>
           </el-table-column>
-          <el-table-column :label="t('inspect.needDevice')" align="center" prop="needDevice" min-width="100">
+          <el-table-column
+            :label="t('inspect.needDevice')"
+            align="center"
+            prop="needDevice"
+            min-width="100"
+          >
             <template #default="scope">
-              <el-tag  type="success"> {{scope.row.needDevice}}</el-tag>
+              <el-tag type="success"> {{ scope.row.needDevice }}</el-tag>
             </template>
           </el-table-column>
-          <el-table-column :label="t('inspect.exceptionCount')" align="center" prop="exceptionCount" min-width="100">
+          <el-table-column
+            :label="t('inspect.exceptionCount')"
+            align="center"
+            prop="exceptionCount"
+            min-width="100"
+          >
             <template #default="scope">
-              <el-tag  type="danger"> {{scope.row.exceptionCount}}</el-tag>
+              <el-tag type="danger"> {{ scope.row.exceptionCount }}</el-tag>
             </template>
           </el-table-column>
-<!--          <el-table-column label="备注" align="center" prop="remark" />-->
+          <!--          <el-table-column label="备注" align="center" prop="remark" />-->
           <el-table-column
             :label="t('inspect.generateTime')"
             align="center"
@@ -135,9 +175,16 @@
             :formatter="dateFormatter"
             min-width="180px"
           />
-          <el-table-column :label="t('iotMaintain.operation')" align="center" min-width="160px" fixed="right">
+          <el-table-column
+            :label="t('iotMaintain.operation')"
+            align="center"
+            min-width="160px"
+            fixed="right"
+          >
             <template #default="scope">
-              <el-button link type="primary" @click="openForm(scope.row.id)"> {{ t('operationFill.view') }} </el-button>
+              <el-button link type="primary" @click="openForm(scope.row.id)">
+                {{ t('operationFill.view') }}
+              </el-button>
               <el-button
                 v-if="scope.row.status === 'todo'"
                 link
@@ -147,8 +194,15 @@
               >
                 {{ t('operationFill.fill') }}
               </el-button>
-              <el-button link type="warning" v-if="scope.row.status==='todo'" @click="openDialog(scope.row.id)"> {{ t('inspect.ignore') }} </el-button>
-<!--              <el-button link type="primary" @click="openForm(scope.row.id)"> {{ t('inspect.ignore') }} </el-button>-->
+              <el-button
+                link
+                type="warning"
+                v-if="scope.row.status === 'todo'"
+                @click="openDialog(scope.row.id)"
+              >
+                {{ t('inspect.ignore') }}
+              </el-button>
+              <!--              <el-button link type="primary" @click="openForm(scope.row.id)"> {{ t('inspect.ignore') }} </el-button>-->
             </template>
           </el-table-column>
         </el-table>
@@ -160,12 +214,7 @@
           append-to-body
           :close-on-click-modal="false"
         >
-          <el-form
-            ref="reasonFormRef"
-            :model="form"
-            :rules="rules"
-            label-width="60px"
-          >
+          <el-form ref="reasonFormRef" :model="form" :rules="rules" label-width="60px">
             <el-form-item label="理由" prop="reason">
               <el-input
                 type="textarea"
@@ -204,7 +253,6 @@ import IotInspectOrderForm from './IotInspectOrderForm.vue'
 import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 import DeptTree from '@/views/system/user/DeptTree.vue'
 
-
 const { push } = useRouter()
 const { params } = useRoute()
 /** 巡检工单 列表 */
@@ -215,10 +263,10 @@ const { t } = useI18n() // 国际化
 const loading = ref(true) // 列表的加载中
 const list = ref<IotInspectOrderVO[]>([]) // 列表的数据
 const status = params.status
-const deptId= params.deptId;
-const createTime = params.createTime;
+const deptId = params.deptId
+const createTime = params.createTime
 const total = ref(0) // 列表的总页数
-const dialogVisible = ref(false);
+const dialogVisible = ref(false)
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
@@ -260,11 +308,11 @@ const resetQuery = () => {
   handleQuery()
 }
 
-const reasonFormRef = ref(null);
+const reasonFormRef = ref(null)
 const form = reactive({
   id: undefined,
-  reason: '',
-});
+  reason: ''
+})
 
 // 表单验证规则
 const rules = {
@@ -272,44 +320,44 @@ const rules = {
     { required: true, message: '请输入忽略理由', trigger: 'blur' },
     { min: 2, message: '理由长度不能少于2个字符', trigger: 'blur' }
   ]
-};
+}
 // 打开对话框
-const openDialog = (id:number) => {
-  dialogVisible.value = true;
-  form.id = id;
-  form.reason = '';
-};
+const openDialog = (id: number) => {
+  dialogVisible.value = true
+  form.id = id
+  form.reason = ''
+}
 
 // 关闭对话框前的回调
 const handleClose = () => {
-  resetForm();
-};
+  resetForm()
+}
 
 // 取消按钮处理
 const handleCancel = () => {
-  dialogVisible.value = false;
-  resetForm();
-};
+  dialogVisible.value = false
+  resetForm()
+}
 
 // 确定按钮处理
 const handleConfirm = async () => {
   // 表单验证
   try {
-    await reasonFormRef.value.validate();
+    await reasonFormRef.value.validate()
     // 验证通过,调用接口
-      await IotInspectOrderApi.orderReason(form)
-      ElMessage.success('操作成功');
-      dialogVisible.value = false;
-      resetForm();
+    await IotInspectOrderApi.orderReason(form)
+    ElMessage.success('操作成功')
+    dialogVisible.value = false
+    resetForm()
   } catch (error) {
-    return;
+    return
   }
-};
+}
 
 // 重置表单
 const resetForm = () => {
-  reasonFormRef.value?.resetFields();
-};
+  reasonFormRef.value?.resetFields()
+}
 
 /** 添加/修改操作 */
 const formRef = ref()
@@ -359,17 +407,17 @@ onMounted(() => {
   if (params.status) {
     queryParams.status = params.status
   }
-  if(deptId != null){
-    queryParams.deptId = deptId;
+  if (deptId != null) {
+    queryParams.deptId = deptId
   }
 
-  if(createTime){
-    queryParams.createTime = createTime;
+  if (createTime) {
+    queryParams.createTime = createTime
   }
   getList()
 })
 </script>
-<style scoped>
+<style scoped scss>
 .reason-confirm-container {
   margin: 20px;
 }
@@ -381,6 +429,12 @@ onMounted(() => {
 
 /* 调整表单项间距 */
 :deep(.el-form-item) {
-  margin-bottom: 15px;
+  margin-bottom ::v-deep .el-table__header-wrapper {
+    position: sticky !important;
+    width: 100%;
+    top: 0px;
+    z-index: 2000;
+  }
+  :15px;
 }
 </style>

+ 20 - 5
src/views/pms/maintain/index.vue

@@ -143,7 +143,13 @@
 
       <!-- 列表 -->
       <ContentWrap>
-        <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+        <el-table
+          v-loading="loading"
+          :data="list"
+          height="65vh"
+          :stripe="true"
+          :show-overflow-tooltip="true"
+        >
           <el-table-column :label="t('maintain.serial')" min-width="70" align="center">
             <template #default="scope">
               {{ scope.$index + 1 }}
@@ -153,13 +159,13 @@
             :label="t('iotDevice.code')"
             align="center"
             prop="deviceCode"
-            min-width="180"
+            width="180"
           />
           <el-table-column
             :label="t('maintain.deviceName')"
             align="center"
             prop="deviceName"
-            min-width="280"
+            width="280"
           />
           <el-table-column
             :label="t('maintain.status')"
@@ -261,7 +267,7 @@
                 {{ t('maintain.maintainWay') }}
               </el-button>
               <el-button
-                v-if="scope.row.status!=='personnel'"
+                v-if="scope.row.status !== 'personnel'"
                 link
                 type="warning"
                 @click="openRecord(scope.row.id)"
@@ -416,7 +422,7 @@ const openDialog = (id: number) => {
 }
 
 const openRecord = async (id?: number) => {
-  await push({ name: 'MaintainRecord', params: {id} })
+  await push({ name: 'MaintainRecord', params: { id } })
 }
 
 const openForm = async (type: string, id?: number, person: any, company) => {
@@ -498,3 +504,12 @@ onMounted(() => {
   userid.value = useUserStore().getUser.id
 })
 </script>
+
+<style lang="scss" scoped>
+::v-deep .el-table__header-wrapper {
+  position: sticky !important;
+  width: 100%;
+  top: 0px;
+  z-index: 2000;
+}
+</style>

+ 132 - 115
src/views/pms/stat/inspect.vue

@@ -51,8 +51,12 @@
                       </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-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>
@@ -60,36 +64,32 @@
   </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 text-gray-400">
-          <span>昨日工单数量</span>
-          <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
-        </div>
-        <el-divider />
-        <div class="flex justify-between items-center mb-1">
-            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
-            >总数量</span
-            >
-          <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
-          >未完成</span
-          >
-        </div>
-        <div class="flex justify-between items-center mb-1">
+      <el-card class="stat-card" shadow="never">
+        <div class="flex flex-col">
+          <div class="flex justify-between items-center text-gray-400">
+            <span>昨日工单数量</span>
+            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
+          </div>
+          <el-divider />
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px">总数量</span>
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px">未完成</span>
+          </div>
+          <div class="flex justify-between items-center mb-1">
             <span class="text-3xl font-bold text-gray-700">
               {{ day.total }}
             </span>
-          <span class="text-3xl font-bold text-gray-700">
+            <span class="text-3xl font-bold text-gray-700">
               {{ day.todo }}
             </span>
+          </div>
+          <!--          <el-divider class="my-2" />-->
+          <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
+          <!--            <span>今日新增</span>-->
+          <!--            <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
+          <!--          </div>-->
         </div>
-        <!--          <el-divider class="my-2" />-->
-        <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
-        <!--            <span>今日新增</span>-->
-        <!--            <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
-        <!--          </div>-->
-      </div>
-    </el-card>
+      </el-card>
     </el-col>
     <el-col :span="6">
       <el-card class="stat-card" shadow="never">
@@ -100,12 +100,8 @@
           </div>
           <el-divider />
           <div class="flex justify-between items-center mb-1">
-            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
-            >总数量</span
-            >
-            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
-            >未完成</span
-            >
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px">总数量</span>
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px">未完成</span>
           </div>
           <div class="flex justify-between items-center mb-1">
             <span class="text-3xl font-bold text-gray-700">
@@ -132,12 +128,8 @@
           </div>
           <el-divider />
           <div class="flex justify-between items-center mb-1">
-            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
-            >总数量</span
-            >
-            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
-            >未完成</span
-            >
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px">总数量</span>
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px">未完成</span>
           </div>
           <div class="flex justify-between items-center mb-1">
             <span class="text-3xl font-bold text-gray-700">
@@ -164,12 +156,8 @@
           </div>
           <el-divider />
           <div class="flex justify-between items-center mb-1">
-            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
-            >总数量</span
-            >
-            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
-            >未完成</span
-            >
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px">总数量</span>
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px">未完成</span>
           </div>
           <div class="flex justify-between items-center mb-1">
             <span class="text-3xl font-bold text-gray-700">
@@ -197,19 +185,34 @@
           <el-col :span="8" class="flex flex-col items-center">
             <div ref="finishedChartRef" class="h-[160px] w-full"></div>
             <div class="text-center mt-2">
-              <span class="text-sm text-gray-600" style="text-decoration: underline;color: dodgerblue;cursor: pointer" @click="clickStatus('工单已执行')">已执行</span>
+              <span
+                class="text-sm text-gray-600"
+                style="text-decoration: underline; color: dodgerblue; cursor: pointer"
+                @click="clickStatus('工单已执行')"
+                >已执行</span
+              >
             </div>
           </el-col>
           <el-col :span="8" class="flex flex-col items-center">
             <div ref="writeChartRef" class="h-[160px] w-full"></div>
             <div class="text-center mt-2">
-              <span class="text-sm text-gray-600" style="text-decoration: underline;color: orangered;cursor: pointer" @click="clickStatus('工单待执行')">待执行</span>
+              <span
+                class="text-sm text-gray-600"
+                style="text-decoration: underline; color: orangered; cursor: pointer"
+                @click="clickStatus('工单待执行')"
+                >待执行</span
+              >
             </div>
           </el-col>
           <el-col :span="8" class="flex flex-col items-center">
             <div ref="ignoreChartRef" class="h-[160px] w-full"></div>
             <div class="text-center mt-2">
-              <span class="text-sm text-gray-600" style="text-decoration: underline;color: purple;cursor: pointer" @click="clickStatus('工单忽略')">忽略</span>
+              <span
+                class="text-sm text-gray-600"
+                style="text-decoration: underline; color: purple; cursor: pointer"
+                @click="clickStatus('工单忽略')"
+                >忽略</span
+              >
             </div>
           </el-col>
         </el-row>
@@ -226,19 +229,34 @@
           <el-col :span="8" class="flex flex-col items-center">
             <div ref="finishedTodayChartRef" class="h-[160px] w-full"></div>
             <div class="text-center mt-2">
-              <span class="text-sm text-gray-600" style="text-decoration: underline;color: dodgerblue;cursor: pointer" @click="clickStatus('设备已执行')">已填写</span>
+              <span
+                class="text-sm text-gray-600"
+                style="text-decoration: underline; color: dodgerblue; cursor: pointer"
+                @click="clickStatus('设备已执行')"
+                >已填写</span
+              >
             </div>
           </el-col>
           <el-col :span="8" class="flex flex-col items-center">
             <div ref="writeTodayChartRef" class="h-[160px] w-full"></div>
-            <div class="text-center mt-2" >
-              <span class="text-sm text-gray-600" style="text-decoration: underline;color: orangered;cursor: pointer" @click="clickStatus('设备待执行')">未填写</span>
+            <div class="text-center mt-2">
+              <span
+                class="text-sm text-gray-600"
+                style="text-decoration: underline; color: orangered; cursor: pointer"
+                @click="clickStatus('设备待执行')"
+                >未填写</span
+              >
             </div>
           </el-col>
           <el-col :span="8" class="flex flex-col items-center">
             <div ref="ignoreTodayChartRef" class="h-[160px] w-full"></div>
-            <div class="text-center mt-2" >
-              <span class="text-sm text-gray-600" style="text-decoration: underline;color: purple;cursor: pointer" @click="clickStatus('设备忽略')">忽略</span>
+            <div class="text-center mt-2">
+              <span
+                class="text-sm text-gray-600"
+                style="text-decoration: underline; color: purple; cursor: pointer"
+                @click="clickStatus('设备忽略')"
+                >忽略</span
+              >
             </div>
           </el-col>
         </el-row>
@@ -275,7 +293,7 @@
 
 <script setup lang="ts" name="Index">
 import * as echarts from 'echarts/core'
-import { BarChart } from 'echarts/charts'; // 显式导入柱状图模块
+import { BarChart } from 'echarts/charts' // 显式导入柱状图模块
 
 import {
   GridComponent,
@@ -294,12 +312,11 @@ import {
 const { currentRoute, push } = useRouter()
 import { formatDate } from '@/utils/formatTime'
 import { IotStatApi } from '@/api/pms/stat'
-import {defaultProps, handleTree} from "@/utils/tree";
-import {ref} from "vue";
-import {DeptTreeItem} from "@/api/system/dept";
-import * as DeptApi from "@/api/system/dept";
-import {useUserStore} from "@/store/modules/user";
-
+import { defaultProps, handleTree } from '@/utils/tree'
+import { ref } from 'vue'
+import { DeptTreeItem } from '@/api/system/dept'
+import * as DeptApi from '@/api/system/dept'
+import { useUserStore } from '@/store/modules/user'
 
 // TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
 
@@ -372,38 +389,38 @@ const resetQuery = () => {
   // 重置查询参数
   queryParams.startTime = Date.now() - 7 * 24 * 60 * 60 * 1000
   queryParams.endTime = Date.now()
-  queryParams.deptId = useUserStore().getUser.deptId;
+  queryParams.deptId = useUserStore().getUser.deptId
   queryParams.createTime = null
 
   // 重新获取数据
   getStats()
 }
-const handleDateChange=()=>{
+const handleDateChange = () => {
   handleQuery()
 }
 const clickStatus = (type) => {
-  let status = '';
-  const createTime = queryParams.createTime;
-  const deptId = queryParams.deptId;
-  debugger
+  let status = ''
+  const createTime = queryParams.createTime
+  const deptId = queryParams.deptId
+
   if (type === '工单待执行') {
     status = 'todo'
-    push({ name: 'IotInspectOrdere', params:{deptId,status,createTime}})
+    push({ name: 'IotInspectOrdere', params: { deptId, status, createTime } })
   } else if (type === '工单已执行') {
     status = 'finished'
-    push({name: 'IotInspectOrdere', params:{deptId,status,createTime}})
-  } else if (type === '工单忽略'){
+    push({ name: 'IotInspectOrdere', params: { deptId, status, createTime } })
+  } else if (type === '工单忽略') {
     status = 'ignore'
-    push({name: 'IotInspectOrdere', params:{deptId,status,createTime}})
+    push({ name: 'IotInspectOrdere', params: { deptId, status, createTime } })
   } else if (type === '设备忽略') {
     status = 'ignore'
-    push({name:'IotInspectOrderDetailStat', params:{deptId,status,createTime}})
-  } else if (type==='设备待执行') {
+    push({ name: 'IotInspectOrderDetailStat', params: { deptId, status, createTime } })
+  } else if (type === '设备待执行') {
     status = 'todo'
-    push({name:'IotInspectOrderDetailStat', params:{deptId,status,createTime}})
-  } else if (type==='设备已执行'){
+    push({ name: 'IotInspectOrderDetailStat', params: { deptId, status, createTime } })
+  } else if (type === '设备已执行') {
     status = 'finished'
-    push({name:'IotInspectOrderDetailStat', params:{deptId,status,createTime}})
+    push({ name: 'IotInspectOrderDetailStat', params: { deptId, status, createTime } })
   }
 }
 
@@ -425,14 +442,14 @@ const total = ref({
 })
 
 const status = ref({
-    finished: 0,
-    todo: 0,
-  ignore:0
+  finished: 0,
+  todo: 0,
+  ignore: 0
 })
 const todayStatus = ref({
   finished: 0,
   todo: 0,
-  ignore:0
+  ignore: 0
 })
 const typeData = ref({})
 // 消息统计数据
@@ -512,7 +529,7 @@ const getStats = async () => {
     initChart()
     initCharts()
   })
-  IotStatApi.getInspectItemStatus(queryParams).then((res) =>{
+  IotStatApi.getInspectItemStatus(queryParams).then((res) => {
     typeData.value = res
     initChart()
     initCharts()
@@ -528,7 +545,7 @@ const getStats = async () => {
 
 /** 初始化图表 */
 const initCharts = () => {
-  const chart = echarts.init(statusChartRef.value);
+  const chart = echarts.init(statusChartRef.value)
   chart.setOption({
     tooltip: {
       trigger: 'item'
@@ -537,8 +554,8 @@ const initCharts = () => {
       // top: '5%',
       // right: '10%',
       // align: 'center',
-      orient: 'horizontal',  // 水平排列图例项
-      bottom: '0%',         // 放置在底部
+      orient: 'horizontal', // 水平排列图例项
+      bottom: '0%', // 放置在底部
       icon: 'circle'
     },
     series: [
@@ -564,16 +581,17 @@ const initCharts = () => {
         },
         data: typeData.value
       }
-    ]})
+    ]
+  })
   chart.on('click', (params) => {
-    console.log('点击的数据值为:', params.value);
-    console.log('点击的数据类型为:', params.data.type);
+    console.log('点击的数据值为:', params.value)
+    console.log('点击的数据类型为:', params.data.type)
     debugger
-    const createTime = queryParams.createTime;
-    const deptId = queryParams.deptId;
+    const createTime = queryParams.createTime
+    const deptId = queryParams.deptId
     const status = params.data.name
-    push({ name: 'IotInspectItemStat', params:{deptId,status,createTime}})
-  });
+    push({ name: 'IotInspectItemStat', params: { deptId, status, createTime } })
+  })
   // echarts.init(statusChartRef.value).setOption({
   // })
   //待执行
@@ -584,10 +602,10 @@ const initCharts = () => {
     '设备待执行'
   )
   initGaugeChart(
-      ignoreTodayChartRef.value,
-      todayStatus.value.ignore === undefined ? 0 : todayStatus.value.ignore,
-      'purple',
-      '设备忽略'
+    ignoreTodayChartRef.value,
+    todayStatus.value.ignore === undefined ? 0 : todayStatus.value.ignore,
+    'purple',
+    '设备忽略'
   )
   //已执行
   initGaugeChart(
@@ -623,7 +641,7 @@ const initCharts = () => {
 
 /** 初始化仪表盘图表 */
 const initGaugeChart = (el: any, value: number, color: string, type: string) => {
-  const chart = echarts.init(el);
+  const chart = echarts.init(el)
   chart.setOption({
     series: [
       {
@@ -665,35 +683,35 @@ const initGaugeChart = (el: any, value: number, color: string, type: string) =>
         data: [{ value: value, type: type }]
       }
     ]
-  });
+  })
 
   // 添加点击事件监听器
   chart.on('click', (params) => {
-    console.log('点击的数据值为:', params.value);
-    console.log('点击的数据类型为:', params.data.type);
-    const createTime = queryParams.createTime;
-    const deptId = queryParams.deptId;
-    let status = '';
+    console.log('点击的数据值为:', params.value)
+    console.log('点击的数据类型为:', params.data.type)
+    const createTime = queryParams.createTime
+    const deptId = queryParams.deptId
+    let status = ''
     if (params.data.type === '工单待执行') {
       status = 'todo'
-      push({ name: 'IotInspectOrdere', params:{deptId,status,createTime}})
+      push({ name: 'IotInspectOrdere', params: { deptId, status, createTime } })
     } else if (params.data.type === '工单已执行') {
       status = 'finished'
-      push({name: 'IotInspectOrdere', params:{deptId,status,createTime}})
-    } else if (params.data.type === '工单忽略'){
+      push({ name: 'IotInspectOrdere', params: { deptId, status, createTime } })
+    } else if (params.data.type === '工单忽略') {
       status = 'ignore'
-      push({name: 'IotInspectOrdere', params:{deptId,status,createTime}})
-    } else if (params.data.type==='设备待执行') {
+      push({ name: 'IotInspectOrdere', params: { deptId, status, createTime } })
+    } else if (params.data.type === '设备待执行') {
       status = 'todo'
-      push({name:'IotInspectOrderDetailStat', params:{deptId,status,createTime}})
-    } else if (params.data.type==='设备已执行'){
+      push({ name: 'IotInspectOrderDetailStat', params: { deptId, status, createTime } })
+    } else if (params.data.type === '设备已执行') {
       status = 'finished'
-      push({name:'IotInspectOrderDetailStat', params:{deptId,status,createTime}})
+      push({ name: 'IotInspectOrderDetailStat', params: { deptId, status, createTime } })
     } else if (params.data.type === '设备忽略') {
       status = 'ignore'
-      push({name:'IotInspectOrderDetailStat', params:{deptId,status,createTime}})
+      push({ name: 'IotInspectOrderDetailStat', params: { deptId, status, createTime } })
     }
-  });
+  })
 }
 
 /** 初始化消息统计图表 */
@@ -838,8 +856,8 @@ const fetchChartData = async () => {
     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],
+        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)
   })
@@ -922,7 +940,7 @@ const handleResize = () => {
 /** 初始化 */
 onMounted(async () => {
   deptList.value = handleTree(await DeptApi.getSimpleDeptList())
-  queryParams.deptId = useUserStore().getUser.deptId;
+  queryParams.deptId = useUserStore().getUser.deptId
   await getStats()
   // await initChart()
   // await initCharts()
@@ -933,5 +951,4 @@ onUnmounted(() => {
 })
 </script>
 
-<style lang="scss" scoped>
-</style>
+<style lang="scss" scoped></style>

+ 564 - 0
src/views/report-statistics/device_book/index2.vue

@@ -0,0 +1,564 @@
+<template style="background-color: #edf4ff">
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1" v-if="treeShow" style="border: 0; height: 87.5vh">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="contentSpan" :xs="24">
+      <!-- 统计卡片 -->
+      <el-row :gutter="21" class="mb-4">
+        <el-col :span="9">
+          <div class="flex gap-2 bg-white p-5 rounded-lg">
+            <div class="stat-card bg-blue-gradient w-[40%]">
+              <Icon icon="ep:histogram" :size="40" />
+              <div class="card-title">设备总数</div>
+              <div class="card-value pt-5">{{ devicesCount }}</div>
+            </div>
+
+            <div class="stat-card bg-green-gradient w-[60%]">
+              <div class="flex flex-col items-center h-full">
+                <!-- <Icon icon="ep:odometer" :size="40" class="mb-2" /> -->
+                <div class="card-title mb-2">设备状态</div>
+                <div class="flex-1 w-full" style="height: calc(100% - 60px)">
+                  <Echart
+                    v-if="statusChartOption.series && statusChartOption.series[0].data.length > 0"
+                    :options="statusChartOption"
+                    :height="160"
+                  />
+                  <div v-else class="flex items-center justify-center h-full text-gray-500">
+                    暂无数据
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="10">
+          <div style="background-color: #fff" class="rounded-lg">
+            <Echart :options="myoption" :height="270" />
+          </div>
+        </el-col>
+
+        <el-col :span="5">
+          <div class="bg-[#fff] p-2 py-5 h-[270px] rounded-lg">
+            <el-form ref="queryFormRef" :model="queryParams">
+              <el-form-item label="设备编码" prop="deviceCode">
+                <el-input
+                  style="height: 30px"
+                  @keyup.enter="handleQuery"
+                  v-model="queryParams.deviceCode"
+                  placeholder="请输入设备编码"
+                />
+              </el-form-item>
+
+              <el-form-item label="设备名称" prop="deviceName">
+                <el-input
+                  style="height: 30px"
+                  @keyup.enter="handleQuery"
+                  v-model="queryParams.deviceName"
+                  placeholder="请输入设备名称"
+                />
+              </el-form-item>
+
+              <el-form-item>
+                <el-button @click="handleQuery"
+                  ><Icon icon="ep:search" class="mr-3px" />{{
+                    t('operationFill.search')
+                  }}</el-button
+                >
+              </el-form-item>
+
+              <el-form-item>
+                <el-button @click="resetQuery"
+                  ><Icon icon="ep:refresh" class="mr-3px" />
+                  {{ t('operationFill.reset') }}</el-button
+                >
+              </el-form-item>
+              <el-form-item>
+                <el-button type="success" plain
+                  ><Icon icon="ep:download" class="mr-3px" /> 导出</el-button
+                >
+              </el-form-item>
+            </el-form>
+          </div>
+        </el-col>
+      </el-row>
+      <!-- 列表 -->
+      <ContentWrap style="border: 0; margin-top: 20px">
+        <el-table
+          v-loading="loading"
+          :data="list"
+          :stripe="true"
+          :show-overflow-tooltip="true"
+          @sort-change="handleSortChange"
+          ref="tableRef"
+          :height="tableHeight"
+        >
+          <el-table-column :label="t('iotDevice.serial')" width="70" align="center">
+            <template #default="scope">
+              {{ scope.$index + 1 }}
+            </template>
+          </el-table-column>
+
+          <el-table-column label="设备编码" sortable align="center" prop="deviceCode" width="150" />
+          <el-table-column
+            :label="t('iotDevice.name')"
+            sortable
+            align="center"
+            prop="deviceName"
+            min-width="250"
+          >
+            <template #default="scope">
+              <el-link :underline="false" type="primary" @click="handleDetail(scope.row.id)">
+                {{ scope.row.deviceName }}
+              </el-link>
+            </template>
+          </el-table-column>
+          <el-table-column
+            :label="t('iotDevice.dept')"
+            align="center"
+            prop="deptName"
+            min-width="150"
+          />
+          <el-table-column
+            :label="t('iotDevice.status')"
+            align="center"
+            prop="deviceStatus"
+            min-width="90"
+          >
+            <template #default="scope">
+              <dict-tag :type="DICT_TYPE.PMS_DEVICE_STATUS" :value="scope.row.deviceStatus" />
+            </template>
+          </el-table-column>
+          <el-table-column
+            :label="t('iotDevice.assets')"
+            align="center"
+            prop="assetProperty"
+            min-width="110"
+          >
+            <template #default="scope">
+              <dict-tag :type="DICT_TYPE.PMS_ASSET_PROPERTY" :value="scope.row.assetProperty" />
+            </template>
+          </el-table-column>
+          <el-table-column
+            :label="t('iotDevice.assetClass')"
+            align="center"
+            prop="assetClassName"
+            min-width="170"
+          />
+          <el-table-column label="生产厂家" align="center" prop="manufacturer" min-width="200" />
+          <el-table-column label="生产日期" align="center" min-width="200">
+            <template #default="scope">
+              {{ formatDate(scope.row.manDate).substring(0, 10) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="投运日期" align="center" min-width="200">
+            <template #default="scope">
+              {{ formatDate(scope.row.enableDate).substring(0, 10) }}
+            </template>
+          </el-table-column>
+          <el-table-column
+            :label="t('deviceForm.brand')"
+            align="center"
+            prop="brandName"
+            min-width="150"
+          />
+          <el-table-column
+            :label="t('deviceForm.model')"
+            align="center"
+            prop="model"
+            min-width="170"
+          />
+          <el-table-column
+            :label="t('devicePerson.rp')"
+            align="center"
+            prop="chargeName"
+            min-width="170"
+          />
+          <el-table-column
+            :label="t('deviceForm.useProject')"
+            align="center"
+            prop="useProject"
+            min-width="170"
+          />
+          <el-table-column
+            :label="t('deviceForm.assetOwner')"
+            align="center"
+            prop="assetOwnership"
+            min-width="170"
+          />
+          <!-- <el-table-column
+            :label="t('operationFill.operation')"
+            align="center"
+            min-width="180px"
+            fixed="right"
+          >
+            <template #default="scope">
+              <el-button
+                link
+                type="primary"
+                @click="openForm('update', scope.row.id)"
+                v-hasPermi="['rq:iot-device:update']"
+              >
+                {{ t('iotDevice.update') }}
+              </el-button>
+              <el-button
+                link
+                type="danger"
+                @click="handleDelete(scope.row.id)"
+                v-hasPermi="['rq:iot-device:delete']"
+              >
+                {{ t('iotDevice.delete') }}
+              </el-button>
+            </template>
+          </el-table-column> -->
+        </el-table>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+</template>
+
+<script setup lang="ts">
+import download from '@/utils/download'
+import { IotDeviceApi, IotDeviceVO } from '@/api/pms/device'
+import { DICT_TYPE } from '@/utils/dict'
+import DeptTree from '@/views/system/user/DeptTree.vue'
+import { buildSortingField } from '@/utils'
+import { useRefreshStore } from '@/store/modules/pms/refreshStore'
+import Echart from '@/components/Echart/src/Echart.vue'
+import echarts from '@/plugins/echarts'
+import { formatDate } from '@/utils/formatTime'
+import { nextTick } from 'vue'
+
+/** 设备台账 列表 */
+defineOptions({ name: 'IotDevicePms' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const { push } = useRouter() // 路由跳转
+
+const refreshStore = useRefreshStore()
+const loading = ref(true) // 列表的加载中
+const ifShow = ref(false)
+const list = ref<IotDeviceVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deviceCode: undefined,
+  deviceName: undefined,
+  brand: undefined,
+  brandName: undefined,
+  model: undefined,
+  deptId: undefined,
+  deviceStatus: undefined,
+  assetProperty: undefined,
+  picUrl: undefined,
+  remark: undefined,
+  manufacturerId: undefined,
+  supplierId: undefined,
+  manDate: [],
+  nameplate: undefined,
+  expires: undefined,
+  plPrice: undefined,
+  plDate: [],
+  plYear: undefined,
+  plStartDate: [],
+  plMonthed: undefined,
+  plAmounted: undefined,
+  remainAmount: undefined,
+  infoId: undefined,
+  infoType: undefined,
+  infoName: undefined,
+  infoRemark: undefined,
+  infoUrl: undefined,
+  templateJson: undefined,
+  creator: undefined,
+  sortingFields: [],
+  assetClass: undefined,
+  yfDeviceCode: undefined
+})
+const queryFormRef = ref(null) // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+const contentSpan = ref(20)
+const treeShow = ref(true)
+
+const handleSortChange = (params: any) => {
+  //console.log(`排序字段: ${prop}, 排序方式: ${order}`);
+  queryParams.sortingFields = []
+  queryParams.sortingFields = [buildSortingField(params)]
+  getList()
+}
+
+const myoption = computed(() => {
+  return {
+    tooltip: {
+      trigger: 'axis'
+    },
+    xAxis: {
+      type: 'category',
+      data: deviceClassify.value.map((item) => item.category),
+      axisLabel: { color: '#000', rotate: 50 }
+    },
+    yAxis: {
+      name: '分类top',
+      type: 'value'
+    },
+    series: [
+      {
+        data: deviceClassify.value.map((item) => item.value),
+        type: 'bar',
+        barWidth: 20,
+        barCategoryGap: '30%',
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#83bff6' },
+            { offset: 0.5, color: '#188df0' },
+            { offset: 1, color: '#188df0' }
+          ])
+        },
+        emphasis: {
+          itemStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: '#2378f7' },
+              { offset: 0.7, color: '#2378f7' },
+              { offset: 1, color: '#83bff6' }
+            ])
+          }
+        }
+      }
+    ]
+  }
+})
+
+let tableRef = ref(null)
+let tableHeight = ref(450)
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotDeviceApi.getIotDeviceList(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  deviceCountQueryParams.deptId = row.id
+  await getList()
+  await getDeviceCount()
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+const handleDetail = (id: number) => {
+  push({ name: 'DeviceDetailInfo', params: { id } })
+}
+
+// 设备数量
+let devicesCount = ref(0)
+const deviceCountQueryParams = reactive({
+  deptId: undefined
+})
+
+// 设备状态
+let deviceStatus = ref([])
+const statusChartOption = computed(() => {
+  // 将 deviceStatus 转换为饼图需要的格式
+  const data = deviceStatus.value.map((item) => ({
+    value: item.value,
+    name: item.name
+  }))
+
+  // 如果没有数据,返回空配置
+  if (data.length === 0) {
+    return {
+      series: [
+        {
+          type: 'pie',
+          data: []
+        }
+      ]
+    }
+  }
+
+  return {
+    tooltip: {
+      trigger: 'item',
+      formatter: '{a} <br/>{b}: {c} ({d}%)'
+    },
+    legend: {
+      show: false
+    },
+    series: [
+      {
+        name: ' ',
+        type: 'pie',
+        radius: ['40%', '70%'],
+        avoidLabelOverlap: false,
+        itemStyle: {
+          borderRadius: 5,
+          borderColor: '#fff',
+          borderWidth: 2
+        },
+        label: {
+          show: true,
+          formatter: '{b}\n{d}%',
+          fontSize: 10
+        },
+        labelLine: {
+          show: true
+        },
+        data: data,
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 10,
+            shadowOffsetX: 0,
+            shadowColor: 'rgba(0, 0, 0, 0.5)'
+          }
+        }
+      }
+    ]
+  }
+})
+// 分类top10
+let deviceClassify = ref([])
+const getDeviceCount = async () => {
+  devicesCount.value = await IotDeviceApi.getIotDeviceCount(deviceCountQueryParams)
+  deviceStatus.value = await IotDeviceApi.getIotDeviceStatus(deviceCountQueryParams)
+  deviceClassify.value = await IotDeviceApi.getIotDeviceClassify(deviceCountQueryParams)
+}
+
+// 计算表格高度
+const calculateTableHeight = () => {
+  if (!tableRef.value) return
+  let h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
+  tableHeight.value = h - 460
+}
+
+// 防抖处理(防止频繁触发)
+const debounce = (fn, delay) => {
+  let timer
+  return (...args) => {
+    clearTimeout(timer)
+    timer = setTimeout(() => fn(...args), delay)
+  }
+}
+/** 初始化 **/
+onMounted(async () => {
+  nextTick(() => {
+    calculateTableHeight()
+    // 使用防抖避免频繁触发
+    const handleResize = debounce(calculateTableHeight, 150)
+    window.addEventListener('resize', handleResize)
+  })
+
+  const sort = {
+    field: 'sortColumn',
+    order: 'asc'
+  }
+  queryParams.sortingFields.push(sort)
+  await getList()
+
+  refreshStore.registerCallback('devicerouter', getList)
+})
+</script>
+
+<style scoped>
+.stat-card {
+  padding: 20px;
+  border-radius: 10px;
+  color: white;
+  text-align: center;
+  font-size: 14px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  transition:
+    transform 0.3s ease,
+    box-shadow 0.3s ease;
+  backdrop-filter: blur(12px);
+  height: 230px;
+}
+.stat-card::before {
+  position: absolute;
+  filter: blur(20px);
+  z-index: -1;
+}
+
+.stat-card:hover {
+  transform: translateY(-4px);
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
+}
+
+.card-title {
+  margin: 8px 0;
+  font-size: 16px;
+}
+
+.card-value {
+  font-size: 28px;
+  font-weight: bold;
+  margin: 8px 0;
+}
+
+.card-trend {
+  font-size: 12px;
+  opacity: 0.9;
+}
+
+/* 毛玻璃渐变背景 —— 不再使用 background-image */
+.bg-blue-gradient {
+  background: linear-gradient(135deg, rgba(77, 147, 255, 0.5), rgba(75, 132, 254));
+}
+
+.bg-green-gradient {
+  background: linear-gradient(135deg, rgba(101, 226, 136, 0.1), #52d7a2);
+  background-color: rgba(76, 175, 80, 0.1);
+}
+
+.bg-orange-gradient {
+  background: linear-gradient(135deg, #ff9800, #f57c00);
+  background-color: rgba(255, 152, 0, 0.5);
+}
+
+/* 确保内容不溢出 */
+:deep(.el-row) {
+  flex-wrap: wrap;
+}
+
+.text-truncate {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+::v-deep .el-table__header-wrapper {
+  position: sticky !important;
+  width: 100%;
+  top: 0px;
+  z-index: 2000;
+}
+</style>

+ 472 - 0
src/views/report-statistics/inspection_order/index.vue

@@ -0,0 +1,472 @@
+<template>
+  <el-row :gutter="20">
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1" style="border: 0">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <!-- 统计卡片 -->
+      <el-row :gutter="18" class="mb-4">
+        <el-col :span="4">
+          <div style="background-color: #fff; border-radius: 10px">
+            <div class="stat-card bg-blue-gradient">
+              <Icon icon="ep:histogram" :size="40" />
+              <div class="card-title">工单数量</div>
+              <div class="card-value pt-5">{{
+                deviceCount.finished + deviceCount.ignore + deviceCount.todo
+              }}</div>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="4">
+          <div style="background-color: #fff; border-radius: 10px">
+            <div class="stat-card bg-green-gradient">
+              <Icon icon="ep:finished" :size="40" />
+              <div class="card-title">完成数量</div>
+              <div class="card-value pt-5">{{ deviceCount.finished }}</div>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="4">
+          <div style="background-color: #fff; border-radius: 10px">
+            <div class="stat-card bg-orange-gradient">
+              <Icon icon="ep:more-filled" :size="40" />
+              <div class="card-title">未完成数量</div>
+              <div class="card-value pt-5">{{ deviceCount.todo }}</div>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="4">
+          <div style="background-color: #fff; border-radius: 10px">
+            <div class="stat-card bg-green-gradient">
+              <Icon icon="ep:hide" :size="40" />
+              <div class="card-title">已忽略</div>
+              <div class="card-value pt-5">{{ deviceCount.ignore }}</div>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="4">
+          <div style="background-color: #fff; border-radius: 10px">
+            <div class="stat-card bg-red-gradient">
+              <Icon icon="ep:bell" :size="40" />
+              <div class="card-title">异常设备数量</div>
+              <div class="card-value pt-5">{{ exceptions?.exceptionNum || 0 }}</div>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="4">
+          <div style="background-color: #fff; border-radius: 10px">
+            <div class="stat-card bg-warn-gradient">
+              <Icon icon="ep:info-filled" :size="40" />
+              <div class="card-title">异常点数量</div>
+              <div class="card-value pt-5">{{ exceptionPoint.value }}</div>
+            </div>
+          </div>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-radio-group v-model="dateType" size="default" fill="#409eff">
+            <el-radio-button label="年" value="year" />
+            <el-radio-button label="月" value="month" />
+            <el-radio-button label="日" value="day" />
+          </el-radio-group>
+        </el-col>
+      </el-row>
+
+      <!-- 列表 -->
+      <ContentWrap style="border: 0; margin-top: 10px">
+        <el-table
+          v-loading="loading"
+          :row-class-name="tableRowClassName"
+          :data="list"
+          :show-overflow-tooltip="true"
+          @row-click="handleRowClick"
+          height="48vh"
+        >
+          <el-table-column :label="t('iotDevice.serial')" width="70" align="center">
+            <template #default="scope">
+              {{ scope.$index + 1 }}
+            </template>
+          </el-table-column>
+          <el-table-column
+            :label="t('bomList.name')"
+            align="center"
+            prop="inspectOrderTitle"
+            min-width="300"
+          />
+
+          <el-table-column label="项目" align="center" prop="project" min-width="110" />
+          <el-table-column label="公司" align="center" prop="company" min-width="110" />
+
+          <el-table-column
+            :label="t('route.orderType')"
+            align="center"
+            prop="type"
+            min-width="90"
+          />
+          <el-table-column
+            :label="t('operationFill.status')"
+            align="center"
+            prop="status"
+            min-width="95"
+          >
+            <template #default="scope">
+              <dict-tag :type="DICT_TYPE.PMS_INSPECT_ORDER_STATUS" :value="scope.row.status" />
+            </template>
+          </el-table-column>
+
+          <el-table-column label="设备明细" align="center" prop="deviceInfo" min-width="110" />
+
+          <el-table-column
+            :label="t('iotMaintain.PersonInCharge')"
+            align="center"
+            prop="chargeName"
+            min-width="110"
+          />
+
+          <!-- 
+          <el-table-column
+            :label="t('inspect.generateTime')"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            min-width="180px"
+          />
+          <el-table-column
+            :label="t('inspect.executeTime')"
+            align="center"
+            prop="executeDate"
+            :formatter="dateFormatter"
+            min-width="180px"
+          /> -->
+          <el-table-column
+            :label="t('iotMaintain.operation')"
+            align="center"
+            min-width="160px"
+            fixed="right"
+          >
+            <template #default="scope">
+              <el-button link type="primary" @click="openForm(scope.row.id)">
+                异常巡检点
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+</template>
+
+<script setup lang="ts">
+import { IotInspectOrderApi, IotInspectOrderVO } from '@/api/pms/inspect/order'
+import { DICT_TYPE } from '@/utils/dict'
+import DeptTree from '@/views/system/user/DeptTree.vue'
+import { watch } from 'vue'
+
+const { push } = useRouter()
+const { params } = useRoute()
+/** 巡检工单 列表 */
+defineOptions({ name: 'IotInspectOrder' })
+
+const { t } = useI18n() // 国际化
+const loading = ref(true) // 列表的加载中
+const list = ref<IotInspectOrderVO[]>([]) // 列表的数据
+const deptId = params.deptId
+const createTime = params.createTime
+const total = ref(0) // 列表的总页数
+
+let dateType = ref('year')
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  inspectOrderTitle: undefined,
+  inspectOrderCode: undefined,
+  status: undefined,
+  remark: undefined,
+  createTime: [],
+  deptId: undefined,
+  deviceIds: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+
+let exceptions = ref(null)
+let exceptionPoint = ref([])
+let deviceCount = ref([])
+
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+  await getCounts()
+}
+
+const tableRowClassName = ({ row }) => {
+  if (row.exceptionCount > 0) {
+    return 'exception-row'
+  }
+  return ''
+}
+
+const handleRowClick = (row, column: any, event: Event) => {
+  // 只有当行有异常时才允许点击跳转
+  if (row.exceptionCount > 0) {
+    // 阻止事件冒泡,避免与按钮点击冲突
+    event.stopPropagation()
+
+    push({
+      name: 'IotInspectItemStat',
+      query: { orderName: row.inspectOrderTitle }
+    })
+  }
+}
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotInspectOrderApi.getIotInspectOrderList(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+watch(
+  dateType,
+  () => {
+    const now = new Date()
+    let startTime: Date
+    let endTime: Date
+
+    switch (dateType.value) {
+      case 'year':
+        // 当年:1月1日 00:00:00 到 12月31日 23:59:59
+        startTime = new Date(now.getFullYear(), 0, 1, 0, 0, 0, 0)
+        endTime = new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999)
+        break
+
+      case 'month':
+        // 当月:月初 00:00:00 到 月末 23:59:59
+        startTime = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0)
+        // 下个月的第0天就是当月的最后一天
+        endTime = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999)
+        break
+
+      case 'day':
+        // 当日:当天 00:00:00 到 23:59:59
+        const year = now.getFullYear()
+        const month = now.getMonth()
+        const date = now.getDate()
+        startTime = new Date(year, month, date, 0, 0, 0, 0)
+        endTime = new Date(year, month, date, 23, 59, 59, 999)
+        break
+
+      default:
+        startTime = new Date(now.getFullYear(), 0, 1, 0, 0, 0, 0)
+        endTime = new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999)
+    }
+
+    // 使用本地时间格式化函数,避免时区转换问题
+    const formatLocalDateTime = (date: 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}`
+    }
+    // 设置查询参数 - 使用本地时间格式化
+    queryParams.createTime = [formatLocalDateTime(startTime), formatLocalDateTime(endTime)]
+
+    // 重新获取数据
+    getList()
+    getCounts()
+  },
+  {
+    immediate: true
+  }
+)
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+const reasonFormRef = ref(null)
+const form = reactive({
+  id: undefined,
+  reason: ''
+})
+
+// 重置表单
+const resetForm = () => {
+  reasonFormRef.value?.resetFields()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (id?: number) => {
+  push({ name: 'InspectOrderDetail', params: { id } })
+}
+
+async function getCounts() {
+  exceptions.value = await IotInspectOrderApi.getIotInspectOrderExceptionDeviceCount({
+    deptId: queryParams.deptId,
+    createTime: queryParams.createTime
+  })
+
+  exceptionPoint.value = await IotInspectOrderApi.getIotInspectOrderExceptionPointCount({
+    deptId: queryParams.deptId,
+    createTime: queryParams.createTime
+  })
+
+  deviceCount.value = await IotInspectOrderApi.getInspectItemStatus({
+    deptId: queryParams.deptId,
+    createTime: queryParams.createTime
+  })
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getCounts()
+  if (params.status) {
+    queryParams.status = params.status
+  }
+  if (deptId != null) {
+    queryParams.deptId = deptId
+  }
+
+  if (createTime) {
+    queryParams.createTime = createTime
+  }
+  getList()
+})
+</script>
+<style scoped>
+/* 添加异常行高亮样式 */
+:deep(.exception-row) {
+  background-color: #f29a90 !important;
+}
+
+:deep(.exception-row:hover) {
+  background-color: #fff0e0 !important;
+}
+
+.reason-confirm-container {
+  margin: 20px;
+}
+
+/* 调整文本框样式 */
+:deep(.el-textarea__inner) {
+  font-size: 14px;
+}
+
+/* 调整表单项间距 */
+:deep(.el-form-item) {
+  margin-bottom: 15px;
+}
+
+.stat-card {
+  padding: 20px;
+  border-radius: 10px;
+  color: white;
+  text-align: center;
+  font-size: 14px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  transition:
+    transform 0.3s ease,
+    box-shadow 0.3s ease;
+  backdrop-filter: blur(12px);
+  height: 200px;
+}
+.stat-card::before {
+  position: absolute;
+  filter: blur(20px);
+  z-index: -1;
+}
+
+.stat-card:hover {
+  transform: translateY(-4px);
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
+}
+
+.card-title {
+  margin: 8px 0;
+  font-size: 16px;
+}
+
+.card-value {
+  font-size: 28px;
+  font-weight: bold;
+  margin: 8px 0;
+}
+
+.card-trend {
+  font-size: 12px;
+  opacity: 0.9;
+}
+
+/* 毛玻璃渐变背景 —— 不再使用 background-image */
+.bg-blue-gradient {
+  background: linear-gradient(135deg, rgba(77, 147, 255, 0.5), rgba(75, 132, 254));
+}
+
+.bg-green-gradient {
+  background: linear-gradient(135deg, rgba(101, 226, 136, 0.1), #52d7a2);
+  background-color: rgba(76, 175, 80, 0.1);
+}
+
+.bg-orange-gradient {
+  background: linear-gradient(135deg, rgba(152, 82, 4, 0.5), rgba(255, 152, 4, 0.6));
+}
+
+.bg-red-gradient {
+  background: linear-gradient(135deg, rgba(232, 65, 51), rgba(252, 242, 236));
+}
+
+.bg-warn-gradient {
+  background: linear-gradient(135deg, rgba(255, 201, 103), rgba(243, 162, 152));
+}
+
+/* 确保内容不溢出 */
+:deep(.el-row) {
+  flex-wrap: wrap;
+}
+
+.text-truncate {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+::v-deep .el-table__header-wrapper {
+  position: sticky !important;
+  width: 100%;
+  top: 0px;
+  z-index: 2000;
+}
+</style>

+ 29 - 27
src/views/system/user/DeptTree.vue

@@ -1,12 +1,18 @@
 <template>
-  <div class="head-container" style="display: flex;flex-direction: row;">
-    <el-input v-model="deptName" class="mb-18px" clearable placeholder="请输入部门名称">
+  <div class="head-container" style="display: flex; flex-direction: row">
+    <el-input
+      v-model="deptName"
+      class="mb-18px"
+      style="height: 35px"
+      clearable
+      placeholder="请输入部门名称"
+    >
       <template #prefix>
         <Icon icon="ep:search" />
       </template>
     </el-input>
   </div>
-  <div ref="treeContainer" class="tree-container">
+  <div ref="treeContainer" class="tree-container" style="border: 0">
     <el-tree
       ref="treeRef"
       :data="deptList"
@@ -21,11 +27,7 @@
       style="height: 52em"
     />
   </div>
-  <div
-    v-show="menuVisible"
-    class="custom-menu"
-    :style="{ left: menuX + 'px', top: menuY + 'px' }"
-  >
+  <div v-show="menuVisible" class="custom-menu" :style="{ left: menuX + 'px', top: menuY + 'px' }">
     <ul>
       <li @click="handleMenuClick('add')">新增子节点</li>
       <li @click="handleMenuClick('edit')">重命名</li>
@@ -44,20 +46,20 @@ defineOptions({ name: 'SystemUserDeptTree' })
 const deptName = ref('')
 const deptList = ref<Tree[]>([]) // 树形结构
 const treeRef = ref<InstanceType<typeof ElTree>>()
-const menuVisible = ref(false);
-const menuX = ref(0);
-const menuY = ref(0);
+const menuVisible = ref(false)
+const menuX = ref(0)
+const menuY = ref(0)
 const firstLevelKeys = ref([])
-let selectedNode = null;
-const treeStore = useTreeStore();
+let selectedNode = null
+const treeStore = useTreeStore()
 
 const handleRightClick = (event, { node, data }) => {
-  event.preventDefault();
-  menuX.value = event.clientX;
-  menuY.value = event.clientY;
-  selectedNode = data; // 存储当前操作的节点数据 ‌:ml-citation{ref="7" data="citationList"}
+  event.preventDefault()
+  menuX.value = event.clientX
+  menuY.value = event.clientY
+  selectedNode = data // 存储当前操作的节点数据 ‌:ml-citation{ref="7" data="citationList"}
   //menuVisible.value = true;
-};
+}
 const treeContainer = ref(null)
 const setHeight = () => {
   if (!treeContainer.value) return
@@ -66,25 +68,25 @@ const setHeight = () => {
   treeContainer.value.style.height = `${windowHeight * 0.78}px` // 60px 底部预留
 }
 const handleMenuClick = (action) => {
-  switch(action) {
+  switch (action) {
     case 'add':
       // 调用新增节点逻辑 ‌:ml-citation{ref="4" data="citationList"}
-      break;
+      break
     case 'edit':
       // 调用编辑节点逻辑 ‌:ml-citation{ref="7" data="citationList"}
-      break;
+      break
     case 'delete':
       // 调用删除节点逻辑 ‌:ml-citation{ref="4" data="citationList"}
-      break;
+      break
   }
-  menuVisible.value = false;
-};
+  menuVisible.value = false
+}
 /** 获得部门树 */
 const getTree = async () => {
   const res = await DeptApi.getSimpleDeptList()
   deptList.value = []
   deptList.value.push(...handleTree(res))
-  firstLevelKeys.value = deptList.value.map(node => node.id);
+  firstLevelKeys.value = deptList.value.map((node) => node.id)
 }
 
 /** 基于名字过滤 */
@@ -96,7 +98,7 @@ const filterNode = (name: string, data: Tree) => {
 /** 处理部门被点击 */
 const handleNodeClick = async (row: { [key: string]: any }) => {
   emits('node-click', row)
-  treeStore.setSelectedId(row.id);
+  treeStore.setSelectedId(row.id)
 }
 const emits = defineEmits(['node-click'])
 
@@ -120,7 +122,7 @@ onUnmounted(() => {
   position: fixed;
   background: white;
   border: 1px solid #ccc;
-  box-shadow: 2px 2px 5px rgba(0,0,0,0.1);
+  box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1);
   z-index: 1000;
 }
 .custom-menu ul {