Forráskód Böngészése

应检证书统计卡片

yanghao 1 hete
szülő
commit
7c223f0360
3 módosított fájl, 260 hozzáadás és 92 törlés
  1. 1 1
      .env.local
  2. 4 0
      src/api/pms/qhse/index.ts
  3. 255 91
      src/views/pms/qhse/deviceCert/index.vue

+ 1 - 1
.env.local

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

+ 4 - 0
src/api/pms/qhse/index.ts

@@ -601,6 +601,10 @@ export const InspectDeviceCertApi = {
   // 导出设备证书 Excel
   exportInspectDeviceCert: async (params) => {
     return await request.download({ url: `/rq/qhse-device-cert/export-excel`, params })
+  },
+  // 统计
+  getInspectDeviceCertStat: async (id) => {
+    return await request.get({ url: `/rq/qhse-device-cert/stat?deptId=${id}` })
   }
 }
 

+ 255 - 91
src/views/pms/qhse/deviceCert/index.vue

@@ -11,99 +11,145 @@
       request-api="getSimpleDeptList"
       @node-click="handleDeptNodeClick" />
 
-    <div class="mb-1">
-      <el-form
-        :model="queryParams"
-        ref="queryFormRef"
-        class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-2 pt-4 flex items-center flex-wrap min-w-0">
-        <div class="flex items-center gap-4 flex-wrap">
-          <el-form-item label="设备名称" prop="deviceName">
-            <el-input
-              v-model="queryParams.deviceName"
-              placeholder="请输入设备名称"
-              clearable
-              class="!w-150px" />
-          </el-form-item>
-
-          <el-form-item label="检测类型" prop="certType">
-            <el-select
-              v-model="queryParams.certType"
-              placeholder="请选择检测类型"
-              clearable
-              style="width: 150px">
-              <el-option
-                v-for="dict in getStrDictOptions(DICT_TYPE.QHSE_DEVICE_CERT_TYPE)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="序列号" prop="certNo">
-            <el-input
-              v-model="queryParams.certNo"
-              placeholder="请输入序列号"
-              clearable
-              class="!w-150px" />
-          </el-form-item>
-          <el-form-item label="检测日期" prop="certTime">
-            <el-date-picker
-              v-model="queryParams.certTime"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              type="daterange"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              class="!w-200px" />
-          </el-form-item>
-          <el-form-item label="检测单位" prop="certOrg">
-            <el-input
-              v-model="queryParams.certOrg"
-              placeholder="请输入检测单位"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-150px" />
-          </el-form-item>
-          <el-form-item label="有效期" prop="certExpire">
-            <el-date-picker
-              v-model="queryParams.certExpire"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              type="daterange"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              class="!w-200px" />
-          </el-form-item>
-
-          <el-form-item>
-            <el-button @click="handleQuery" v-hasPermi="['rq:qhse-device-cert:query']">
-              <Icon icon="ep:search" class="mr-5px" /> 搜索
-            </el-button>
-            <el-button @click="resetQuery">
-              <Icon icon="ep:refresh" class="mr-5px" /> 重置
-            </el-button>
-            <el-button
-              v-hasPermi="['rq:qhse-device-cert:create']"
-              type="primary"
-              plain
-              @click="openForm('create')">
-              <Icon icon="ep:plus" class="mr-5px" /> 新增
-            </el-button>
-            <el-button
-              v-hasPermi="['rq:qhse-device-cert:export']"
-              type="success"
-              plain
-              @click="handleExport"
-              :loading="exportLoading">
-              <Icon icon="ep:download" class="mr-5px" /> 导出
-            </el-button>
-          </el-form-item>
+    <div class="stats-cards">
+      <div
+        class="stats-card stats-card--expired stats-card--clickable"
+        @click="handleStatCardClick('expired')">
+        <div class="flex items-center gap-2">
+          <Icon icon="ep:info-filled" color="#de3b3b" />
+          <div class="stats-card__label">已过期</div>
+        </div>
+
+        <div class="stats-card__value">
+          <CountTo
+            :duration="2600"
+            :end-val="expired"
+            :start-val="0"
+            class="stats-card__value text-[40px]! pt-10 text-center! text-[#e35656]!" />
+        </div>
+      </div>
+      <div
+        class="stats-card stats-card--warn stats-card--clickable"
+        @click="handleStatCardClick('warn')">
+        <div class="flex items-center gap-2">
+          <Icon icon="ep:bell-filled" color="#d97706" />
+          <div class="stats-card__label">90天预警(含过期)</div>
+        </div>
+        <div class="stats-card__value">
+          <CountTo
+            :duration="2600"
+            :end-val="warn"
+            :start-val="0"
+            class="stats-card__value text-[40px]! pt-10 text-center! text-[#d97706]!" />
         </div>
-      </el-form>
+      </div>
+      <div
+        class="stats-card stats-card--total stats-card--clickable"
+        @click="handleStatCardClick('total')">
+        <div class="flex items-center gap-2">
+          <Icon icon="eos-icons:counting" color="#2563eb" />
+          <div class="stats-card__label">证书总数</div>
+        </div>
+
+        <div class="stats-card__value">
+          <CountTo
+            :duration="2600"
+            :end-val="totalCert"
+            :start-val="0"
+            class="stats-card__value text-[40px]! pt-10 text-center! text-[#2563eb]!" />
+        </div>
+      </div>
     </div>
 
-    <div class="min-w-0"></div>
+    <el-form
+      :model="queryParams"
+      ref="queryFormRef"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-2 pt-4 flex items-center flex-wrap min-w-0">
+      <div class="flex items-center gap-2 flex-wrap">
+        <el-form-item label="设备名称" prop="deviceName">
+          <el-input
+            v-model="queryParams.deviceName"
+            placeholder="请输入设备名称"
+            clearable
+            class="!w-150px" />
+        </el-form-item>
+
+        <el-form-item label="检测类型" prop="certType">
+          <el-select
+            v-model="queryParams.certType"
+            placeholder="请选择检测类型"
+            clearable
+            style="width: 150px">
+            <el-option
+              v-for="dict in getStrDictOptions(DICT_TYPE.QHSE_DEVICE_CERT_TYPE)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="序列号" prop="certNo">
+          <el-input
+            v-model="queryParams.certNo"
+            placeholder="请输入序列号"
+            clearable
+            class="!w-150px" />
+        </el-form-item>
+        <el-form-item label="检测日期" prop="certTime">
+          <el-date-picker
+            v-model="queryParams.certTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-200px" />
+        </el-form-item>
+        <el-form-item label="检测单位" prop="certOrg">
+          <el-input
+            v-model="queryParams.certOrg"
+            placeholder="请输入检测单位"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-150px" />
+        </el-form-item>
+        <el-form-item label="有效期" prop="certExpire">
+          <el-date-picker
+            v-model="queryParams.certExpire"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-200px" />
+        </el-form-item>
+
+        <el-form-item>
+          <el-button @click="handleQuery" v-hasPermi="['rq:qhse-device-cert:query']">
+            <Icon icon="ep:search" class="mr-5px" /> 搜索
+          </el-button>
+          <el-button @click="resetQuery">
+            <Icon icon="ep:refresh" class="mr-5px" /> 重置
+          </el-button>
+          <el-button
+            v-hasPermi="['rq:qhse-device-cert:create']"
+            type="primary"
+            plain
+            @click="openForm('create')">
+            <Icon icon="ep:plus" class="mr-5px" /> 新增
+          </el-button>
+          <el-button
+            v-hasPermi="['rq:qhse-device-cert:export']"
+            type="success"
+            plain
+            @click="handleExport"
+            :loading="exportLoading">
+            <Icon icon="ep:download" class="mr-5px" /> 导出
+          </el-button>
+        </el-form-item>
+      </div>
+    </el-form>
 
-    <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col p-2 pt-4 min-w-0">
+    <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col mt-1 p-2 pt-4 min-w-0">
       <div class="flex-1 relative min-h-0">
         <el-auto-resizer class="absolute">
           <template #default="{ width, height }">
@@ -184,7 +230,7 @@
     </div>
 
     <div
-      class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-3 min-w-0 flex gap-4 justify-between">
+      class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-3 mt-1 min-w-0 flex gap-4 justify-between">
       <div
         class="bg-[#fef0f0] text-[#f57979] px-3 flex-1 flex items-center justify-center text-center py-2 text-sm rounded-md"
         ><el-icon class="stats-card__icon" :size="28"> <Icon icon="ep:info-filled" /> </el-icon
@@ -238,7 +284,7 @@ import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 
 /** 应检设备证书 列表 */
 defineOptions({ name: 'QHSEDeviceCert' })
-// const userStore = useUserStore()
+const userStore = useUserStore()
 const rootDeptId = 156
 const deptId = useUserStore().getUser.deptId || rootDeptId
 
@@ -303,12 +349,38 @@ const handleQuery = () => {
   getList()
 }
 
+const handleStatCardClick = (type: 'expired' | 'warn' | 'total') => {
+  queryParams.pageNo = 1
+
+  if (type === 'expired') {
+    queryParams.expired = true
+    queryParams.alertWarn = undefined
+    queryParams.type = undefined
+    getList()
+    return
+  }
+
+  if (type === 'warn') {
+    queryParams.alertWarn = true
+    queryParams.expired = undefined
+    queryParams.type = undefined
+    getList()
+    return
+  }
+
+  queryParams.type = undefined
+  queryParams.expired = undefined
+  queryParams.alertWarn = undefined
+  getList()
+}
+
 const selectedDept = ref<{ id: number; name: string }>()
 
 const handleDeptNodeClick = async (row) => {
   selectedDept.value = { id: row.id, name: row.name }
   queryParams.deptId = row.id
   await getList()
+  await getStatic()
 }
 
 const formatDateCorrectly = (timestamp) => {
@@ -422,9 +494,28 @@ const handleDownload = async (url) => {
     console.error('下载失败:', error)
   }
 }
+
+let totalCert = ref(0)
+let expired = ref(0)
+let warn = ref(0)
+
+async function getStatic() {
+  if (queryParams.deptId) {
+    const res = await InspectDeviceCertApi.getInspectDeviceCertStat(queryParams.deptId)
+    totalCert.value = res.total
+    expired.value = res.expired
+    warn.value = res.warn
+  } else {
+    const res = await InspectDeviceCertApi.getInspectDeviceCertStat(userStore.user.deptId)
+    totalCert.value = res.total
+    expired.value = res.expired
+    warn.value = res.warn
+  }
+}
 /** 初始化 **/
 onMounted(() => {
   getList()
+  getStatic()
 })
 </script>
 
@@ -438,6 +529,79 @@ onMounted(() => {
   color: var(--el-text-color-primary);
 }
 
+.stats-cards {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 12px;
+  margin-bottom: 5px;
+}
+
+.stats-card {
+  padding: 14px 16px;
+  background: linear-gradient(180deg, #ffffff 0%, #f7faff 100%);
+  border: 1px solid #e4ecf7;
+  border-radius: 10px;
+  box-shadow: 0 4px 12px rgb(31 91 184 / 8%);
+}
+
+.stats-card--clickable {
+  cursor: pointer;
+  transition:
+    transform 0.18s ease,
+    box-shadow 0.18s ease,
+    border-color 0.18s ease;
+}
+
+.stats-card--clickable:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 10px 24px rgb(31 91 184 / 14%);
+}
+
+.stats-card__label {
+  font-size: 14px;
+  font-weight: 600;
+  color: #6b7280;
+  line-height: 1.4;
+}
+
+.stats-card__value {
+  margin-top: 8px;
+  font-size: 28px;
+  font-weight: 700;
+  line-height: 1;
+  color: #1f5bb8;
+}
+
+.stats-card--expired {
+  background: linear-gradient(180deg, #fff4f4 0%, #ffe8e8 100%);
+  border-color: #ffcfcf;
+}
+
+.stats-card--expired .stats-card__value {
+  color: #de3b3b;
+}
+
+.stats-card--warn {
+  background: linear-gradient(180deg, #fff8ef 0%, #ffeed9 100%);
+  border-color: #ffd7a1;
+}
+
+.stats-card--warn .stats-card__value {
+  color: #d97706;
+}
+
+.stats-card--total .stats-card__value {
+  color: #2563eb;
+}
+
+.stats-card--personal .stats-card__value {
+  color: #16a34a;
+}
+
+.stats-card--organization .stats-card__value {
+  color: #7c3aed;
+}
+
 /* 过期行的红色背景 - 基础状态 */
 :deep(.el-table__body tr.expired-row > td.el-table__cell) {
   background-color: #ffe6e6 !important;