yanghao il y a 2 jours
Parent
commit
b0291170ea

+ 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://aims.deepoil.cc'
+VITE_BASE_URL='http://172.21.10.222:8080'
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
 VITE_UPLOAD_TYPE=server

+ 72 - 0
pnpm-lock.yaml

@@ -206,6 +206,9 @@ importers:
       web-storage-cache:
         specifier: ^1.1.1
         version: 1.1.1
+      xlsx:
+        specifier: ^0.18.5
+        version: 0.18.5
       xml-js:
         specifier: ^1.6.11
         version: 1.6.11
@@ -2402,6 +2405,10 @@ packages:
     engines: {node: '>=0.4.0'}
     hasBin: true
 
+  adler-32@1.3.1:
+    resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
+    engines: {node: '>=0.8'}
+
   aes-decrypter@3.1.3:
     resolution: {integrity: sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==}
 
@@ -2605,6 +2612,10 @@ packages:
   caniuse-lite@1.0.30001756:
     resolution: {integrity: sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==}
 
+  cfb@1.2.2:
+    resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
+    engines: {node: '>=0.8'}
+
   chalk@1.1.3:
     resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
     engines: {node: '>=0.10.0'}
@@ -2658,6 +2669,10 @@ packages:
     resolution: {integrity: sha512-HcfnUFJwI2FvH73YWVbbMh7ObWxZiHIycEhv9ZEXy6e8ZKDjtZKbbYFUtsLN46HFXPvU5V2Uvc2d55Z//oFW5A==}
     deprecated: This is an accidentally mis-tagged instance of 5.65.7
 
+  codepage@1.15.0:
+    resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
+    engines: {node: '>=0.8'}
+
   color-convert@2.0.1:
     resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
     engines: {node: '>=7.0.0'}
@@ -2762,6 +2777,11 @@ packages:
       typescript:
         optional: true
 
+  crc-32@1.2.2:
+    resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
+    engines: {node: '>=0.8'}
+    hasBin: true
+
   crelt@1.0.6:
     resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
 
@@ -3390,6 +3410,10 @@ packages:
     resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
     engines: {node: '>= 6'}
 
+  frac@1.1.2:
+    resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
+    engines: {node: '>=0.8'}
+
   fraction.js@4.3.7:
     resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
 
@@ -4716,6 +4740,10 @@ packages:
   sprintf-js@1.0.3:
     resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
 
+  ssf@0.11.2:
+    resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
+    engines: {node: '>=0.8'}
+
   ssr-window@3.0.0:
     resolution: {integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==}
 
@@ -5232,10 +5260,18 @@ packages:
   wildcard@1.1.2:
     resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==}
 
+  wmf@1.0.2:
+    resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==}
+    engines: {node: '>=0.8'}
+
   word-wrap@1.2.5:
     resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
     engines: {node: '>=0.10.0'}
 
+  word@0.3.0:
+    resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
+    engines: {node: '>=0.8'}
+
   wrap-ansi@6.2.0:
     resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
     engines: {node: '>=8'}
@@ -5259,6 +5295,11 @@ packages:
     resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
 
+  xlsx@0.18.5:
+    resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
+    engines: {node: '>=0.8'}
+    hasBin: true
+
   xml-js@1.6.11:
     resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
     hasBin: true
@@ -7788,6 +7829,8 @@ snapshots:
 
   acorn@8.14.0: {}
 
+  adler-32@1.3.1: {}
+
   aes-decrypter@3.1.3:
     dependencies:
       '@babel/runtime': 7.26.0
@@ -8013,6 +8056,11 @@ snapshots:
 
   caniuse-lite@1.0.30001756: {}
 
+  cfb@1.2.2:
+    dependencies:
+      adler-32: 1.3.1
+      crc-32: 1.2.2
+
   chalk@1.1.3:
     dependencies:
       ansi-styles: 2.2.1
@@ -8090,6 +8138,8 @@ snapshots:
 
   codemirror@6.65.7: {}
 
+  codepage@1.15.0: {}
+
   color-convert@2.0.1:
     dependencies:
       color-name: 1.1.4
@@ -8176,6 +8226,8 @@ snapshots:
     optionalDependencies:
       typescript: 5.3.3
 
+  crc-32@1.2.2: {}
+
   crelt@1.0.6: {}
 
   cropperjs@1.6.2: {}
@@ -8920,6 +8972,8 @@ snapshots:
       combined-stream: 1.0.8
       mime-types: 2.1.35
 
+  frac@1.1.2: {}
+
   fraction.js@4.3.7: {}
 
   framer-motion@12.23.12:
@@ -10142,6 +10196,10 @@ snapshots:
 
   sprintf-js@1.0.3: {}
 
+  ssf@0.11.2:
+    dependencies:
+      frac: 1.1.2
+
   ssr-window@3.0.0: {}
 
   steady-xml@0.1.0: {}
@@ -10731,8 +10789,12 @@ snapshots:
 
   wildcard@1.1.2: {}
 
+  wmf@1.0.2: {}
+
   word-wrap@1.2.5: {}
 
+  word@0.3.0: {}
+
   wrap-ansi@6.2.0:
     dependencies:
       ansi-styles: 4.3.0
@@ -10764,6 +10826,16 @@ snapshots:
       imurmurhash: 0.1.4
       signal-exit: 4.1.0
 
+  xlsx@0.18.5:
+    dependencies:
+      adler-32: 1.3.1
+      cfb: 1.2.2
+      codepage: 1.15.0
+      crc-32: 1.2.2
+      ssf: 0.11.2
+      wmf: 1.0.2
+      word: 0.3.0
+
   xml-js@1.6.11:
     dependencies:
       sax: 1.4.1

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

@@ -0,0 +1,164 @@
+import request from '@/config/axios'
+
+// 计量器具-证书管理 VO
+export interface IotMeasureCertVO {
+  id: number // 主键id
+  type: string // 证书类型
+  classify: string // 证书类别
+  certBelong: string // 证书所属公司/个人
+  certOrg: string // 证书颁发机构
+  certStandard: string // 证书标准
+  certIssue: Date // 证书颁发时间
+  certExpire: Date // 证书有效期
+  noticeBefore: number // 到期前提醒
+  certPic: string // 证书图片上传
+  remark: string // 备注
+  deptId: number // 部门id
+}
+
+// 计量器具-证书管理 API
+export const IotMeasureCertApi = {
+  // 查询计量器具-证书管理分页
+  getIotMeasureCertPage: async (params: any) => {
+    return await request.get({ url: `/rq/iot-measure-cert/page`, params })
+  },
+
+  // 查询计量器具-证书管理详情
+  getIotMeasureCert: async (id: number) => {
+    return await request.get({ url: `/rq/iot-measure-cert/get?id=` + id })
+  },
+
+  // 新增计量器具-证书管理
+  createIotMeasureCert: async (data: IotMeasureCertVO) => {
+    return await request.post({ url: `/rq/iot-measure-cert/create`, data })
+  },
+
+  // 修改计量器具-证书管理
+  updateIotMeasureCert: async (data: IotMeasureCertVO) => {
+    return await request.put({ url: `/rq/iot-measure-cert/update`, data })
+  },
+
+  // 删除计量器具-证书管理
+  deleteIotMeasureCert: async (id: number) => {
+    return await request.delete({ url: `/rq/iot-measure-cert/delete?id=` + id })
+  },
+
+  // 导出计量器具-证书管理 Excel
+  exportIotMeasureCert: async (params) => {
+    return await request.download({ url: `/rq/iot-measure-cert/export-excel`, params })
+  }
+}
+
+// 计量器具台账 VO
+export const IotInstrumentApi = {
+  // 获得计量器具台账分页
+  getInstrumentList: async (params) => {
+    return await request.get({ url: `rq/iot-measure-book/page`, params })
+  },
+  // 删除计量器具台账
+  deleteInstrument: async (id) => {
+    return await request.delete({ url: `/rq/iot-measure-book/delete?id=` + id })
+  },
+  // 更新计量器具台账
+  updateInstrument: async (data) => {
+    return await request.put({ url: `/rq/iot-measure-book/update`, data })
+  },
+  // 新增计量器具台账
+  createInstrument: async (data) => {
+    return await request.post({ url: `/rq/iot-measure-book/create`, data })
+  },
+  // 导出计量器具台账 Excel
+  exportInstrument: async (params) => {
+    return await request.download({ url: `/rq/iot-measure-book/export-excel`, params })
+  }
+}
+
+// 计量器具-检测校准明细 VO
+export interface IotMeasureDetectVO {
+  id: number // 主键id
+  measureId: number // 计量器具id
+  detectDate: string // 检测/校准日期
+  detectOrg: string // 检测/校准机构
+  detectContent: string // 检测/校准内容
+  validityPeriod: Date // 检测/校准有效期
+  detectAmount: number // 校准金额
+  deptId: number // 部门id
+}
+
+// 计量器具-检测校准明细 API
+export const IotMeasureDetectApi = {
+  // 查询计量器具-检测校准明细分页
+  getIotMeasureDetectPage: async (params: any) => {
+    return await request.get({ url: `/rq/iot-measure-detect/page`, params })
+  },
+
+  // 查询计量器具-检测校准明细详情
+  getIotMeasureDetect: async (id: number) => {
+    return await request.get({ url: `/rq/iot-measure-detect/get?id=` + id })
+  },
+
+  // 新增计量器具-检测校准明细
+  createIotMeasureDetect: async (data: IotMeasureDetectVO) => {
+    return await request.post({ url: `/rq/iot-measure-detect/create`, data })
+  },
+
+  // 修改计量器具-检测校准明细
+  updateIotMeasureDetect: async (data: IotMeasureDetectVO) => {
+    return await request.put({ url: `/rq/iot-measure-detect/update`, data })
+  },
+
+  // 删除计量器具-检测校准明细
+  deleteIotMeasureDetect: async (id: number) => {
+    return await request.delete({ url: `/rq/iot-measure-detect/delete?id=` + id })
+  },
+
+  // 导出计量器具-检测校准明细 Excel
+  exportIotMeasureDetect: async (params) => {
+    return await request.download({ url: `/rq/iot-measure-detect/export-excel`, params })
+  }
+}
+
+// 计量器具-使用记录 VO
+export interface IotMeasureRecordVO {
+  id: number // 主键id
+  measureId: number // 计量器具id
+  useDate: string // 使用日期
+  useReason: string // 使用原因
+  measureProject: string // 计量项目
+  usePerson: string // 使用人
+  userId: number // 使用人id
+  deptId: number // 部门id
+}
+
+// 计量器具-使用记录 API
+export const IotMeasureRecordApi = {
+  // 查询计量器具-使用记录分页
+  getIotMeasureRecordPage: async (params: any) => {
+    return await request.get({ url: `/rq/iot-measure-record/page`, params })
+  },
+
+  // 查询计量器具-使用记录详情
+  getIotMeasureRecord: async (id: number) => {
+    return await request.get({ url: `/rq/iot-measure-record/get?id=` + id })
+  },
+
+  // 新增计量器具-使用记录
+  createIotMeasureRecord: async (data: IotMeasureRecordVO) => {
+    return await request.post({ url: `/rq/iot-measure-record/create`, data })
+  },
+
+  // 修改计量器具-使用记录
+  updateIotMeasureRecord: async (data: IotMeasureRecordVO) => {
+    return await request.put({ url: `/rq/iot-measure-record/update`, data })
+  },
+
+  // 删除计量器具-使用记录
+  deleteIotMeasureRecord: async (id: number) => {
+    return await request.delete({ url: `/rq/iot-measure-record/delete?id=` + id })
+  },
+
+  // 导出计量器具-使用记录 Excel
+  exportIotMeasureRecord: async (params) => {
+    return await request.download({ url: `/rq/iot-measure-record/export-excel`, params })
+  }
+}

+ 591 - 0
src/views/pms/qhse/certificate.vue

@@ -0,0 +1,591 @@
+<!-- src/views/pms/qhse/certificate.vue -->
+<template>
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1" v-if="treeShow">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="contentSpan" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+          <el-form-item label="证书类型" prop="type">
+            <el-select v-model="queryParams.type" placeholder="请选择证书类型" style="width: 150px">
+              <el-option label="个人证书" value="personal" />
+              <el-option label="组织证书" value="organization" />
+              <el-option label="其他" value="other" />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="证书类别" prop="classify">
+            <el-select
+              v-model="queryParams.classify"
+              placeholder="请选择证书类别"
+              style="width: 150px"
+            >
+              <el-option label="职业资格证" value="professional" />
+              <el-option label="技能证书" value="skill" />
+              <el-option label="学历证书" value="education" />
+              <el-option label="荣誉证书" value="honor" />
+              <el-option label="其他" value="other" />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item>
+            <el-button @click="handleAdd" type="primary"
+              ><Icon icon="ep:plus" class="mr-5px" />新增</el-button
+            >
+            <el-button @click="handleQuery"
+              ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.search') }}</el-button
+            >
+            <el-button @click="resetQuery"
+              ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('devicePerson.reset') }}</el-button
+            >
+            <el-button @click="handleExport" type="success" plain
+              ><Icon icon="ep:download" class="mr-5px" /> 导出Excel</el-button
+            >
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+
+      <!-- 列表 -->
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+          <el-table-column :label="t('monitor.serial')" width="70" align="center">
+            <template #default="scope">
+              {{ scope.$index + 1 }}
+            </template>
+          </el-table-column>
+
+          <el-table-column label="证书类型" align="center" prop="type">
+            <template #default="scope">
+              {{ getCertificateTypeText(scope.row.type) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column label="证书类别" align="center" prop="classify">
+            <template #default="scope">
+              {{ getCertificateCategoryText(scope.row.classify) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column label="所属公司/人" align="center" prop="certBelong" />
+
+          <el-table-column label="颁发机构" align="center" prop="certOrg" />
+
+          <el-table-column label="证书标准" align="center" prop="certStandard" />
+
+          <el-table-column label="颁发时间" align="center" prop="certIssue">
+            <template #default="scope">
+              {{ formatDateCorrectly(scope.row.certIssue) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column label="有效期" align="center">
+            <template #default="scope">
+              {{ formatDateCorrectly(scope.row.certExpire) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column label="到期提醒" align="center">
+            <template #default="scope"> {{ scope.row.noticeBefore }}天前提醒 </template>
+          </el-table-column>
+
+          <el-table-column label="备注" align="center" prop="remark" />
+          <el-table-column
+            :label="t('devicePerson.operation')"
+            align="center"
+            fixed="right"
+            min-width="120px"
+          >
+            <template #default="scope">
+              <el-button link type="primary" @click="handleEdit(scope.row)"> 编辑 </el-button>
+              <el-button link type="danger" @click="handleDelete(scope.row.id)"> 删除 </el-button>
+              <el-button link type="success" @click="handleViewImage(scope.row.image)">
+                查看
+              </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>
+
+  <!-- 新增/编辑证书对话框 -->
+  <el-dialog
+    :title="dialogTitle"
+    v-model="dialogVisible"
+    width="600px"
+    destroy-on-close
+    @close="closeDialog"
+  >
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="120px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="证书类型" prop="type">
+        <el-select v-model="formData.type" placeholder="请选择证书类型">
+          <el-option label="个人证书" value="personal" />
+          <el-option label="组织证书" value="organization" />
+          <el-option label="其他" value="other" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="证书类别" prop="classify">
+        <el-select v-model="formData.classify" placeholder="请选择证书类别">
+          <el-option label="职业资格证" value="professional" />
+          <el-option label="技能证书" value="skill" />
+          <el-option label="学历证书" value="education" />
+          <el-option label="荣誉证书" value="honor" />
+          <el-option label="其他" value="other" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="所属公司/人" prop="certBelong">
+        <el-input v-model="formData.certBelong" placeholder="请输入所属公司或人员" />
+      </el-form-item>
+
+      <el-form-item label="颁发机构" prop="certOrg">
+        <el-input v-model="formData.certOrg" placeholder="请输入颁发机构" />
+      </el-form-item>
+
+      <el-form-item label="证书标准" prop="certStandard">
+        <el-input v-model="formData.certStandard" placeholder="如国标、API等" />
+      </el-form-item>
+
+      <el-form-item label="颁发时间" prop="certIssue">
+        <el-date-picker
+          v-model="formData.certIssue"
+          type="date"
+          value-format="x"
+          placeholder="请选择颁发时间"
+          style="width: 100%"
+        />
+      </el-form-item>
+
+      <el-form-item label="有效期" prop="certExpire">
+        <el-date-picker
+          v-model="formData.certExpire"
+          type="date"
+          value-format="x"
+          placeholder="请选择有效期"
+          style="width: 100%"
+        />
+      </el-form-item>
+
+      <el-form-item label="到期前提醒" prop="noticeBefore">
+        <el-input-number
+          v-model="formData.noticeBefore"
+          :min="0"
+          :max="365"
+          placeholder="请输入提前多少天提醒"
+          style="width: 100%"
+        />
+      </el-form-item>
+
+      <el-form-item label="备注" prop="remark">
+        <el-input
+          type="textarea"
+          v-model="formData.remark"
+          :rows="2"
+          placeholder="请输入备注"
+          style="width: 100%"
+        />
+      </el-form-item>
+
+      <el-form-item label="证书图片" prop="certPic">
+        <el-upload
+          :action="uploadUrl"
+          :headers="uploadHeaders"
+          :on-success="handleUploadSuccess"
+          :on-remove="handleRemove"
+          :auto-upload="false"
+          list-type="picture-card"
+          :limit="1"
+          accept=".jpg,.jpeg,.png,.gif"
+        >
+          <i class="el-icon-plus"></i>
+        </el-upload>
+        <div v-if="formData.certPic" class="image-preview">
+          <img :src="formData.certPic" alt="证书图片" style="max-width: 100px; max-height: 100px" />
+        </div>
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <el-button @click="closeDialog">取 消</el-button>
+      <el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
+    </template>
+  </el-dialog>
+
+  <!-- 查看证书图片对话框 -->
+  <el-dialog :title="imageDialogTitle" v-model="imageDialogVisible" width="800px" center>
+    <img :src="imagePreviewUrl" alt="证书图片" style="max-width: 100%; max-height: 80vh" />
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { IotMeasureCertApi } from '@/api/pms/qhse/index'
+import DeptTree from '@/views/system/user/DeptTree.vue'
+import { handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import { ElMessageBox, ElMessage } from 'element-plus'
+const deptList = ref<Tree[]>([]) // 树形结构
+const deptList2 = ref<Tree[]>([]) // 树形结构
+import { formatDate } from '@/utils/formatTime'
+
+defineOptions({ name: 'IotQHSECertificate' })
+
+const loading = ref(true) // 列表的加载中
+const formLoading = ref(false) // 表单加载中
+const submitLoading = ref(false) // 提交按钮加载中
+const exportLoading = ref(false) // 导出按钮加载中
+
+const { t } = useI18n()
+
+const list = ref([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  type: undefined,
+  classify: undefined,
+  deptId: ''
+})
+const queryFormRef = ref(null) // 搜索的表单
+
+const contentSpan = ref(20)
+const treeShow = ref(true)
+
+// 对话框相关
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const isEdit = ref(false)
+
+// 图片查看对话框
+const imageDialogVisible = ref(false)
+const imageDialogTitle = ref('证书图片')
+const imagePreviewUrl = ref('')
+
+// 表单相关
+const formRef = ref()
+const formData = ref({
+  type: '', // 证书类型
+  classify: '', // 证书类别
+  certBelong: '', // 证书所属公司/个人
+  certOrg: '', // 证书颁发机构
+  certStandard: '', // 证书标准
+  certIssue: '', // 证书颁发时间
+  certExpire: '', // 证书有效期
+  noticeBefore: '', // 到期前提醒
+  certPic: '', // 证书图片上传
+  remark: '', // 备注
+  deptId: '' // 部门id
+})
+
+// 文件上传配置
+const uploadUrl = '/api/upload/file' // 请替换为实际的上传接口
+const uploadHeaders = {
+  Authorization: `Bearer ${localStorage.getItem('token')}`
+}
+
+// 获取证书类型文本
+const getCertificateTypeText = (type: string) => {
+  const map: Record<string, string> = {
+    personal: '个人证书',
+    organization: '组织证书',
+    other: '其他'
+  }
+  return map[type] || type
+}
+
+// 获取证书类别文本
+const getCertificateCategoryText = (category: string) => {
+  const map: Record<string, string> = {
+    professional: '职业资格证',
+    skill: '技能证书',
+    education: '学历证书',
+    honor: '荣誉证书',
+    other: '其他'
+  }
+  return map[category] || category
+}
+
+// 获取状态标签类型
+const getTagType = (status: string) => {
+  if (status === 'valid') return 'success'
+  if (status === 'expired') return 'danger'
+  if (status === 'expiring') return 'warning'
+  return 'info'
+}
+
+// 获取状态文本
+const getStatusText = (status: string) => {
+  const map: Record<string, string> = {
+    valid: '有效',
+    expired: '已过期',
+    expiring: '即将过期',
+    pending: '待审核'
+  }
+  return map[status] || status
+}
+
+// 正确格式化日期的函数
+const formatDateCorrectly = (timestamp) => {
+  if (!timestamp) return ''
+
+  // 如果是秒级时间戳,转换为毫秒级
+  let time = Number(timestamp)
+  if (time < 10000000000) {
+    // 小于这个数通常表示秒级时间戳
+    time = time * 1000
+  }
+
+  return formatDate(time).substring(0, 10)
+}
+
+// 表单验证规则
+const formRules = {
+  type: [{ required: true, message: '证书类型不能为空', trigger: 'blur' }],
+  classify: [{ required: true, message: '证书类别不能为空', trigger: 'blur' }],
+  certBelong: [{ required: true, message: '所属公司/人不能为空', trigger: 'blur' }],
+  certOrg: [{ required: true, message: '颁发机构不能为空', trigger: 'blur' }],
+  certIssue: [{ required: true, message: '颁发时间不能为空', trigger: 'blur' }],
+  certExpire: [{ required: true, message: '有效期不能为空', trigger: 'blur' }]
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotMeasureCertApi.getIotMeasureCertPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleExport = async () => {
+  try {
+    const response = await IotMeasureCertApi.exportIotMeasureCert(queryParams)
+    downloadFile(response)
+  } catch (error) {
+    ElMessage.error('导出失败,请重试')
+    console.error('导出错误:', error)
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 首页处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryParams.deptId = ''
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+// 显示新增对话框
+const handleAdd = () => {
+  isEdit.value = false
+  dialogTitle.value = '新增证书'
+  resetForm()
+  dialogVisible.value = true
+}
+
+// 显示编辑对话框
+const handleEdit = (row) => {
+  isEdit.value = true
+  dialogTitle.value = '编辑证书'
+
+  formData.value = {
+    ...row,
+    // 确保日期字段正确处理
+    issueDate: row.issueDate ? ensureMillisecondTimestamp(row.issueDate) : null,
+    validityPeriod: row.validityPeriod ? ensureMillisecondTimestamp(row.validityPeriod) : null
+  }
+
+  dialogVisible.value = true
+}
+
+// 查看证书图片
+const handleViewImage = (imageUrl: string) => {
+  imagePreviewUrl.value = imageUrl
+  imageDialogTitle.value = '证书图片'
+  imageDialogVisible.value = true
+}
+
+// 处理文件上传成功
+const handleUploadSuccess = (response: any, file: any) => {
+  formData.value.certPic = response.data.url
+}
+
+// 处理文件删除
+const handleRemove = (file: any, fileList: any) => {
+  formData.value.certPic = ''
+}
+
+// 确保时间戳是毫秒级的
+const ensureMillisecondTimestamp = (timestamp) => {
+  let time = Number(timestamp)
+  if (time < 10000000000) {
+    // 秒级时间戳转为毫秒级
+    return time * 1000
+  }
+  return time
+}
+
+//删除证书
+const handleDelete = async (id: number) => {
+  ElMessageBox.confirm('确定要删除该证书吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      try {
+        await IotMeasureCertApi.deleteIotMeasureCert(id)
+        ElMessage.success('删除成功')
+        getList()
+      } catch (error) {
+        console.error(error)
+      }
+    })
+    .catch(() => {
+      // 取消操作
+    })
+}
+
+// 重置表单
+const resetForm = () => {
+  formData.value = {
+    type: '', // 证书类型
+    classify: '', // 证书类别
+    certBelong: '', // 证书所属公司/个人
+    certOrg: '', // 证书颁发机构
+    certStandard: '', // 证书标准
+    certIssue: '', // 证书颁发时间
+    certExpire: '', // 证书有效期
+    noticeBefore: '', // 到期前提醒
+    certPic: '', // 证书图片上传
+    remark: '', // 备注
+    deptId: '' // 部门id
+  }
+  formRef.value?.clearValidate()
+}
+
+// 关闭对话框
+const closeDialog = () => {
+  dialogVisible.value = false
+  resetForm()
+}
+
+// 提交表单
+const submitForm = async () => {
+  if (!formRef.value) return
+
+  try {
+    await formRef.value.validate()
+    submitLoading.value = true
+
+    // 准备提交数据
+    const submitData = {
+      ...formData.value,
+      // 确保日期字段以正确的格式提交
+      certIssue: formData.value.certIssue,
+      certExpire: formData.value.certExpire
+    }
+
+    if (isEdit.value) {
+      // 编辑
+      await IotMeasureCertApi.updateIotMeasureCert(submitData)
+      ElMessage.success('编辑成功')
+    } else {
+      // 新增
+      await IotMeasureCertApi.createIotMeasureCert(submitData)
+      ElMessage.success('新增成功')
+    }
+
+    dialogVisible.value = false
+    getList()
+  } catch (error) {
+    console.error(error)
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+// 下载文件函数
+const downloadFile = (response: any) => {
+  const blob = new Blob([response], {
+    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
+  })
+
+  let fileName = '证书台账.xlsx'
+  const disposition = response.headers ? response.headers['content-disposition'] : ''
+  if (disposition) {
+    const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
+    const matches = filenameRegex.exec(disposition)
+    if (matches != null && matches[1]) {
+      fileName = matches[1].replace(/['"]/g, '')
+    }
+  }
+
+  const url = window.URL.createObjectURL(blob)
+  const link = document.createElement('a')
+  link.href = url
+  link.setAttribute('download', fileName)
+
+  document.body.appendChild(link)
+  link.click()
+
+  document.body.removeChild(link)
+  window.URL.revokeObjectURL(url)
+}
+
+onMounted(async () => {
+  getList()
+
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+})
+</script>
+
+<style scoped>
+::deep(.el-tree--highlight-current) {
+  height: 200px !important;
+}
+::deep(.el-transfer-panel__body) {
+  height: 700px !important;
+}
+.image-preview {
+  margin-top: 10px;
+  display: flex;
+  justify-content: center;
+}
+</style>

+ 525 - 0
src/views/pms/qhse/index.vue

@@ -0,0 +1,525 @@
+<template>
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1" v-if="treeShow">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="contentSpan" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+          <el-form-item label="计量器具名称" prop="measureName">
+            <el-input
+              v-model="queryParams.measureName"
+              placeholder="请输入计量器具名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleAdd" type="primary"
+              ><Icon icon="ep:plus" class="mr-5px" />新增</el-button
+            >
+            <el-button @click="handleQuery"
+              ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.search') }}</el-button
+            >
+            <el-button @click="resetQuery"
+              ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('devicePerson.reset') }}</el-button
+            >
+            <el-button @click="handleExport" type="success" plain
+              ><Icon icon="ep:download" class="mr-5px" /> 导出Excel</el-button
+            >
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+
+      <!-- 列表 -->
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+          <el-table-column :label="t('monitor.serial')" width="70" align="center">
+            <template #default="scope">
+              {{ scope.$index + 1 }}
+            </template>
+          </el-table-column>
+          <el-table-column label="单位" align="center" prop="measureUnit" />
+          <el-table-column label="名称" align="center" prop="measureName" />
+          <el-table-column label="责任人" align="center" prop="dutyPerson" />
+          <el-table-column label="品牌" align="center" prop="brand" />
+          <el-table-column label="规格型号" align="center" prop="modelName" />
+          <el-table-column label="分类" align="center" prop="classify" />
+          <el-table-column label="采购日期" align="center" prop="buyDate">
+            <template #default="scope">
+              {{ formatDateCorrectly(scope.row.buyDate) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="有效期" align="center" prop="validity">
+            <template #default="scope">
+              {{ formatDateCorrectly(scope.row.validity) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="上次检验日期" align="center" prop="lastTime" min-width="150">
+            <template #default="scope">
+              {{ formatDateCorrectly(scope.row.lastTime) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="价格" align="center" prop="measurePrice">
+            <template #default="scope">
+              {{ scope.row.measurePrice }}
+            </template>
+          </el-table-column>
+          <el-table-column label="备注" align="center" prop="remark" />
+
+          <el-table-column
+            :label="t('devicePerson.operation')"
+            align="center"
+            fixed="right"
+            min-width="120px"
+          >
+            <template #default="scope">
+              <el-button link type="primary" @click="handleEdit(scope.row)"> 编辑 </el-button>
+              <el-button link type="danger" @click="handleDelete(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>
+
+  <!-- 新增/编辑台账对话框 -->
+  <el-dialog
+    :title="dialogTitle"
+    v-model="dialogVisible"
+    width="800px"
+    destroy-on-close
+    @close="closeDialog"
+  >
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="120px"
+      v-loading="formLoading"
+    >
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="计量器具名称" prop="measureName">
+            <el-input v-model="formData.measureName" placeholder="请输入计量器具名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="计量器具编码" prop="measureCode">
+            <el-input v-model="formData.measureCode" placeholder="请输入计量器具编码" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="部门" prop="deptId">
+            <el-tree-select
+              clearable
+              v-model="formData.deptId"
+              :data="deptList2"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              filterable
+              placeholder="请选择所在部门"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="责任人" prop="dutyPerson">
+            <el-input v-model="formData.dutyPerson" placeholder="请输入责任人" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="品牌" prop="brand">
+            <el-input v-model="formData.brand" placeholder="请输入品牌" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="规格型号" prop="modelName">
+            <el-input v-model="formData.modelName" placeholder="请输入规格型号" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="分类" prop="classify">
+            <el-input v-model="formData.classify" placeholder="请输入分类" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="单位" prop="measureUnit">
+            <el-input v-model="formData.measureUnit" placeholder="请输入单位" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="采购日期" prop="buyDate">
+            <el-date-picker
+              v-model="formData.buyDate"
+              type="date"
+              value-format="x"
+              placeholder="请选择采购日期"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="价格" prop="measurePrice">
+            <el-input-number
+              v-model="formData.measurePrice"
+              :precision="2"
+              :step="1"
+              placeholder="请输入价格"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="有效期" prop="validity">
+            <el-date-picker
+              v-model="formData.validity"
+              type="date"
+              value-format="x"
+              placeholder="请选择有效期"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="上次检验日期" prop="lastTime">
+            <el-date-picker
+              v-model="formData.lastTime"
+              type="date"
+              value-format="x"
+              placeholder="请选择上次检验/校准日期"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="备注" prop="remark">
+            <el-input v-model="formData.remark" type="textarea" placeholder="请输入描述" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <template #footer>
+      <el-button @click="closeDialog">取 消</el-button>
+      <el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { IotInstrumentApi } from '@/api/pms/qhse/index'
+import DeptTree from '@/views/system/user/DeptTree.vue'
+import { handleTree } from '@/utils/tree'
+import { defaultProps } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import { ElMessageBox } from 'element-plus'
+const deptList = ref<Tree[]>([]) // 树形结构
+const deptList2 = ref<Tree[]>([]) // 树形结构
+import { formatDate } from '@/utils/formatTime'
+
+defineOptions({ name: 'IotQHSEMeasure' })
+
+const loading = ref(true) // 列表的加载中
+const formLoading = ref(false) // 表单加载中
+const submitLoading = ref(false) // 提交按钮加载中
+
+const { t } = useI18n()
+
+const list = ref([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  measureName: undefined,
+  deptId: undefined
+})
+const queryFormRef = ref(null) // 搜索的表单
+
+const contentSpan = ref(20)
+const treeShow = ref(true)
+
+// 对话框相关
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const isEdit = ref(false)
+
+// 表单相关
+const formRef = ref()
+const formData = ref({
+  measureName: '',
+  measureUnit: '',
+  dutyPerson: '',
+  brand: '',
+  modelName: '',
+  classify: '',
+  buyDate: null,
+  remark: '',
+  deptId: '',
+  measureCode: '',
+  validity: null, // 有效期
+  lastTime: null, // 上次检验/校准日期
+  measurePrice: 0 // 价格
+})
+
+// 正确格式化日期的函数
+const formatDateCorrectly = (timestamp) => {
+  if (!timestamp) return ''
+
+  // 如果是秒级时间戳,转换为毫秒级
+  let time = Number(timestamp)
+  if (time < 10000000000) {
+    // 小于这个数通常表示秒级时间戳
+    time = time * 1000
+  }
+
+  return formatDate(time).substring(0, 10)
+}
+
+// 表单验证规则
+const formRules = {
+  measureName: [{ required: true, message: '计量器具名称不能为空', trigger: 'blur' }],
+  dutyPerson: [{ required: true, message: '责任人不能为空', trigger: 'blur' }],
+  classify: [{ required: true, message: '分类不能为空', trigger: 'blur' }],
+  measureCode: [{ required: true, message: '编码不能为空', trigger: 'blur' }]
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotInstrumentApi.getInstrumentList(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const downloadFile = (response: any) => {
+  // 创建 blob 对象
+  const blob = new Blob([response], {
+    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
+  })
+
+  // 获取文件名
+  let fileName = '计量器具台账.xlsx'
+  const disposition = response.headers ? response.headers['content-disposition'] : ''
+  if (disposition) {
+    const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
+    const matches = filenameRegex.exec(disposition)
+    if (matches != null && matches[1]) {
+      fileName = matches[1].replace(/['"]/g, '')
+    }
+  }
+
+  // 创建下载链接
+  const url = window.URL.createObjectURL(blob)
+  const link = document.createElement('a')
+  link.href = url
+  link.setAttribute('download', fileName)
+
+  // 触发下载
+  document.body.appendChild(link)
+  link.click()
+
+  // 清理
+  document.body.removeChild(link)
+  window.URL.revokeObjectURL(url)
+}
+
+const handleExport = async () => {
+  try {
+    // 调用导出接口
+    const response = await IotInstrumentApi.exportInstrument(queryParams)
+
+    // 下载文件
+    downloadFile(response)
+  } catch (error) {
+    ElMessage.error('导出失败,请重试')
+    console.error('导出错误:', error)
+  } finally {
+  }
+}
+
+/** 首页处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+// 显示新增对话框
+const handleAdd = () => {
+  isEdit.value = false
+  dialogTitle.value = '新增台账'
+  resetForm()
+  dialogVisible.value = true
+}
+
+// 显示编辑对话框
+const handleEdit = (row) => {
+  isEdit.value = true
+  dialogTitle.value = '编辑台账'
+
+  formData.value = {
+    ...row,
+    // 确保日期字段正确处理
+    buyDate: row.buyDate ? ensureMillisecondTimestamp(row.buyDate) : null,
+    validity: row.validity ? ensureMillisecondTimestamp(row.validity) : null,
+    lastTime: row.lastTime ? ensureMillisecondTimestamp(row.lastTime) : null
+  }
+
+  dialogVisible.value = true
+}
+
+// 确保时间戳是毫秒级的
+const ensureMillisecondTimestamp = (timestamp) => {
+  let time = Number(timestamp)
+  if (time < 10000000000) {
+    // 秒级时间戳转为毫秒级
+    return time * 1000
+  }
+  return time
+}
+
+//删除成套
+const handleDelete = async (id: number) => {
+  ElMessageBox.confirm('确定要删除该台账吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      try {
+        await IotInstrumentApi.deleteInstrument(id)
+        ElMessage.success('删除成功')
+        getList()
+      } catch (error) {
+        console.error(error)
+      }
+    })
+    .catch(() => {
+      // 取消操作
+    })
+}
+
+// 重置表单
+const resetForm = () => {
+  formData.value = {
+    measureName: '',
+    measureUnit: '',
+    dutyPerson: '',
+    brand: '',
+    modelName: '',
+    classify: '',
+    buyDate: null,
+    remark: '',
+    deptId: '',
+    measureCode: '',
+    validity: null, // 有效期
+    lastTime: null, // 上次检验/校准日期
+    measurePrice: 0 // 价格
+  }
+  formRef.value?.clearValidate()
+}
+
+// 关闭对话框
+const closeDialog = () => {
+  dialogVisible.value = false
+  resetForm()
+}
+
+// 提交表单
+const submitForm = async () => {
+  if (!formRef.value) return
+
+  try {
+    await formRef.value.validate()
+    submitLoading.value = true
+
+    // 准备提交数据
+    const submitData = {
+      ...formData.value,
+      // 确保日期字段以正确的格式提交
+      buyDate: formData.value.buyDate,
+      validity: formData.value.validity,
+      lastTime: formData.value.lastTime
+    }
+
+    if (isEdit.value) {
+      // 编辑
+      await IotInstrumentApi.updateInstrument(submitData)
+      ElMessage.success('编辑成功')
+    } else {
+      // 新增
+      await IotInstrumentApi.createInstrument(submitData)
+      ElMessage.success('新增成功')
+    }
+
+    dialogVisible.value = false
+    getList()
+  } catch (error) {
+    console.error(error)
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+onMounted(async () => {
+  getList()
+
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+})
+</script>
+
+<style scoped>
+::deep(.el-tree--highlight-current) {
+  height: 200px !important;
+}
+::deep(.el-transfer-panel__body) {
+  height: 700px !important;
+}
+</style>

+ 128 - 0
src/views/pms/qhse/iotmeasuredetect/IotMeasureDetectForm.vue

@@ -0,0 +1,128 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="检测/校准日期" prop="detectDate">
+        <el-date-picker
+          v-model="formData.detectDate"
+          type="date"
+          value-format="x"
+          placeholder="选择检测/校准日期"
+        />
+      </el-form-item>
+      <el-form-item label="检测/校准机构" prop="detectOrg">
+        <el-input v-model="formData.detectOrg" placeholder="请输入检测/校准机构" />
+      </el-form-item>
+      <el-form-item label="检测/校准内容" prop="detectContent">
+        <Editor v-model="formData.detectContent" height="150px" />
+      </el-form-item>
+      <el-form-item label="检测/校准有效期" prop="validityPeriod">
+        <el-date-picker
+          v-model="formData.validityPeriod"
+          type="date"
+          value-format="x"
+          placeholder="选择检测/校准有效期"
+        />
+      </el-form-item>
+      <el-form-item label="校准金额" prop="detectAmount">
+        <el-input v-model="formData.detectAmount" placeholder="请输入校准金额" />
+      </el-form-item>
+      <el-form-item label="部门id" prop="deptId">
+        <el-input v-model="formData.deptId" placeholder="请输入部门id" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { IotMeasureDetectApi, IotMeasureDetectVO } from '@/api/pms/qhse/index'
+
+/** 计量器具-检测校准明细 表单 */
+defineOptions({ name: 'IotMeasureDetectForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  detectDate: undefined,
+  detectOrg: undefined,
+  detectContent: undefined,
+  validityPeriod: undefined,
+  detectAmount: undefined,
+  deptId: undefined
+})
+const formRules = reactive({
+  detectDate: [{ required: true, message: '检测/校准日期不能为空', trigger: 'blur' }],
+  detectOrg: [{ required: true, message: '检测/校准机构不能为空', trigger: 'blur' }],
+  detectContent: [{ required: true, message: '检测/校准内容不能为空', trigger: 'blur' }],
+  validityPeriod: [{ required: true, message: '检测/校准有效期不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await IotMeasureDetectApi.getIotMeasureDetect(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as IotMeasureDetectVO
+    if (formType.value === 'create') {
+      await IotMeasureDetectApi.createIotMeasureDetect(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await IotMeasureDetectApi.updateIotMeasureDetect(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    detectDate: undefined,
+    detectOrg: undefined,
+    detectContent: undefined,
+    validityPeriod: undefined,
+    detectAmount: undefined,
+    deptId: undefined
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 229 - 0
src/views/pms/qhse/iotmeasuredetect/index.vue

@@ -0,0 +1,229 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+      <el-form-item label="检测/校准日期" prop="detectDate">
+        <el-date-picker
+          v-model="queryParams.detectDate"
+          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-150px"
+        />
+      </el-form-item>
+      <el-form-item label="检测/校准机构" prop="detectOrg">
+        <el-input
+          v-model="queryParams.detectOrg"
+          placeholder="请输入检测/校准机构"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-150px"
+        />
+      </el-form-item>
+      <el-form-item label="检测/校准有效期" prop="validityPeriod">
+        <el-date-picker
+          v-model="queryParams.validityPeriod"
+          value-format="YYYY-MM-DD"
+          type="date"
+          placeholder="选择检测/校准有效期"
+          clearable
+          class="!w-150px"
+        />
+      </el-form-item>
+      <el-form-item label="校准金额" prop="detectAmount">
+        <el-input
+          v-model="queryParams.detectAmount"
+          placeholder="请输入校准金额"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-150px"
+        />
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-150px"
+        />
+      </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
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['rq:iot-measure-detect:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['rq:iot-measure-detect:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="计量器具id" align="center" prop="measureId" />
+      <el-table-column label="检测/校准日期" align="center" prop="detectDate" />
+      <el-table-column label="检测/校准机构" align="center" prop="detectOrg" />
+      <el-table-column label="检测/校准内容" align="center" prop="detectContent" />
+      <el-table-column
+        label="检测/校准有效期"
+        align="center"
+        prop="validityPeriod"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="校准金额" align="center" prop="detectAmount" />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="部门名称" align="center" prop="deptName" />
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['rq:iot-measure-detect:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['rq:iot-measure-detect: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>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <IotMeasureDetectForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { IotMeasureDetectApi, IotMeasureDetectVO } from '@/api/pms/qhse/index'
+import IotMeasureDetectForm from './IotMeasureDetectForm.vue'
+
+/** 计量器具-检测校准明细 列表 */
+defineOptions({ name: 'IotMeasureDetect' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<IotMeasureDetectVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  measureId: undefined,
+  detectDate: [],
+  detectOrg: undefined,
+  detectContent: undefined,
+  validityPeriod: undefined,
+  detectAmount: undefined,
+  createTime: [],
+  deptId: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotMeasureDetectApi.getIotMeasureDetectPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotMeasureDetectApi.deleteIotMeasureDetect(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotMeasureDetectApi.exportIotMeasureDetect(queryParams)
+    download.excel(data, '计量器具-检测校准明细.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 322 - 0
src/views/pms/qhse/iotmeasurerecord/IotMeasureRecordForm.vue

@@ -0,0 +1,322 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="计量器具" prop="measureId">
+        <el-input v-model="formData.measureName" disabled placeholder="选择计量器具">
+          <template #append>
+            <el-link @click="selectMeasure" :underline="false">选择计量器具</el-link>
+          </template>
+        </el-input>
+      </el-form-item>
+      <el-form-item label="使用日期" prop="useDate">
+        <el-date-picker
+          v-model="formData.useDate"
+          type="date"
+          value-format="x"
+          placeholder="选择使用日期"
+        />
+      </el-form-item>
+      <el-form-item label="使用原因" prop="useReason">
+        <el-input v-model="formData.useReason" placeholder="请输入使用原因" />
+      </el-form-item>
+      <el-form-item label="计量项目" prop="measureProject">
+        <el-input v-model="formData.measureProject" placeholder="请输入计量项目" />
+      </el-form-item>
+      <el-form-item label="使用人" prop="usePerson">
+        <el-input v-model="formData.usePerson" placeholder="请输入使用人" />
+      </el-form-item>
+
+      <el-form-item label="部门" prop="deptId">
+        <el-tree-select
+          clearable
+          v-model="formData.deptId"
+          :data="deptList2"
+          :props="defaultProps"
+          check-strictly
+          node-key="id"
+          filterable
+          placeholder="请选择所在部门"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+
+  <Dialog title="选择仪器" v-model="measureDialogVisible" width="60%">
+    <ContentWrap>
+      <!-- 搜索工作栏 -->
+      <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+        <el-form-item label="计量器具名称" prop="measureName">
+          <el-input
+            v-model="queryParams.measureName"
+            placeholder="请输入计量器具名称"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-200px"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button @click="handleQuery"
+            ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.search') }}</el-button
+          >
+          <el-button @click="resetQuery"
+            ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('devicePerson.reset') }}</el-button
+          >
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+    <div class="pb-10">
+      <el-table
+        v-loading="loading"
+        :data="measureList"
+        :stripe="true"
+        :show-overflow-tooltip="true"
+        ref="measureTableRef"
+      >
+        <el-table-column width="50" align="center">
+          <template #default="scope">
+            <el-radio
+              :model-value="selectedMeasureId"
+              :label="scope.row.id"
+              @change="handleRadioChange(scope.row)"
+            >
+              &nbsp;
+            </el-radio>
+          </template>
+        </el-table-column>
+        <el-table-column :label="t('monitor.serial')" width="70" align="center">
+          <template #default="scope">
+            {{ scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <el-table-column label="单位" align="center" prop="measureUnit" />
+        <el-table-column label="名称" align="center" prop="measureName" />
+        <el-table-column label="责任人" align="center" prop="dutyPerson" />
+        <el-table-column label="品牌" align="center" prop="brand" />
+        <el-table-column label="规格型号" align="center" prop="modelName" />
+        <el-table-column label="分类" align="center" prop="classify" />
+        <el-table-column label="采购日期" align="center" prop="buyDate">
+          <template #default="scope">
+            {{ formatDateCorrectly(scope.row.buyDate) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="有效期" align="center" prop="validity">
+          <template #default="scope">
+            {{ formatDateCorrectly(scope.row.validity) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="上次检验日期" align="center" prop="lastTime" min-width="150">
+          <template #default="scope">
+            {{ formatDateCorrectly(scope.row.lastTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="价格" align="center" prop="measurePrice">
+          <template #default="scope">
+            {{ scope.row.measurePrice }}
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" align="center" prop="remark" />
+      </el-table>
+      <!-- 分页 -->
+      <Pagination
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </div>
+
+    <template #footer>
+      <el-button @click="confirmSelectMeasure" type="primary">确 定</el-button>
+      <el-button @click="measureDialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import { IotMeasureRecordApi, IotMeasureRecordVO, IotInstrumentApi } from '@/api/pms/qhse/index'
+import { onMounted, ref } from 'vue'
+import { formatDate } from '@/utils/formatTime'
+import { handleTree } from '@/utils/tree'
+import { defaultProps } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+
+/** 计量器具-使用记录 表单 */
+defineOptions({ name: 'IotMeasureRecordForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  measureId: undefined,
+  measureName: '', // 添加计量器具名称字段
+  useDate: undefined,
+  useReason: undefined,
+  measureProject: undefined,
+  usePerson: undefined,
+  userId: undefined,
+  deptId: undefined
+})
+const formRules = reactive({
+  measureId: [{ required: true, message: '计量器具不能为空', trigger: 'blur' }],
+  useDate: [{ required: true, message: '使用日期不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+// 仪器选择相关
+const measureDialogVisible = ref(false)
+
+const selectedMeasureId = ref<number | undefined>(undefined) // 当前选中的仪器ID
+const selectedMeasure = ref<any>(null) // 当前选中的仪器对象
+const deptList2 = ref<Tree[]>([]) // 树形结构
+
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await IotMeasureRecordApi.getIotMeasureRecord(id)
+      // 设置选中的仪器ID
+      selectedMeasureId.value = formData.value.measureId
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as IotMeasureRecordVO
+    if (formType.value === 'create') {
+      await IotMeasureRecordApi.createIotMeasureRecord(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await IotMeasureRecordApi.updateIotMeasureRecord(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    measureId: undefined,
+    measureName: '',
+    useDate: undefined,
+    useReason: undefined,
+    measureProject: undefined,
+    usePerson: undefined,
+    userId: undefined,
+    deptId: undefined
+  }
+  formRef.value?.resetFields()
+  selectedMeasureId.value = undefined
+  selectedMeasure.value = null
+}
+
+const resetQuery = () => {
+  queryParams.measureName = undefined
+  queryParams.deptId = undefined
+  handleQuery()
+}
+
+const measureList = ref([])
+let total = ref(0)
+const loading = ref(true) // 列表的加载中
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  measureName: undefined,
+  deptId: undefined
+})
+
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotInstrumentApi.getInstrumentList(queryParams)
+    measureList.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const selectMeasure = () => {
+  measureDialogVisible.value = true
+  getList()
+}
+
+// 处理单选框变化
+const handleRadioChange = (row: any) => {
+  selectedMeasureId.value = row.id
+  selectedMeasure.value = row
+}
+
+// 确认选择仪器
+const confirmSelectMeasure = () => {
+  if (!selectedMeasure.value) {
+    message.warning('请先选择一个计量器具')
+    return
+  }
+
+  // 将选中的仪器信息填入表单
+  formData.value.measureId = selectedMeasure.value.id
+  formData.value.measureName = selectedMeasure.value.measureName
+
+  // 关闭选择仪器对话框
+  measureDialogVisible.value = false
+}
+
+// 正确格式化日期的函数
+const formatDateCorrectly = (timestamp) => {
+  if (!timestamp) return ''
+
+  // 如果是秒级时间戳,转换为毫秒级
+  let time = Number(timestamp)
+  if (time < 10000000000) {
+    // 小于这个数通常表示秒级时间戳
+    time = time * 1000
+  }
+
+  return formatDate(time).substring(0, 10)
+}
+
+onMounted(async () => {
+  deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+})
+</script>

+ 242 - 0
src/views/pms/qhse/iotmeasurerecord/index.vue

@@ -0,0 +1,242 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+      <el-form-item label="计量器具名称" prop="measureName">
+        <el-input
+          v-model="queryParams.measureName"
+          placeholder="请输入计量器具名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-150px"
+        />
+      </el-form-item>
+      <!-- <el-form-item label="使用日期" prop="useDate">
+        <el-date-picker
+          v-model="queryParams.useDate"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-220px"
+        />
+      </el-form-item> -->
+
+      <el-form-item label="计量项目" prop="measureProject">
+        <el-input
+          v-model="queryParams.measureProject"
+          placeholder="请输入计量项目"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-150px"
+        />
+      </el-form-item>
+      <el-form-item label="使用人" prop="usePerson">
+        <el-input
+          v-model="queryParams.usePerson"
+          placeholder="请输入使用人"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-150px"
+        />
+      </el-form-item>
+
+      <!-- <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-220px"
+        />
+      </el-form-item> -->
+      <!-- <el-form-item label="部门名称" prop="deptId">
+        <el-input
+          v-model="queryParams.deptId"
+          placeholder="请输入部门名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </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
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['rq:iot-measure-record:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['rq:iot-measure-record:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="计量器具名称" align="center" prop="measureName" />
+      <el-table-column label="使用日期" align="center" prop="useDate">
+        <template #default="scope">
+          {{ formatDateCorrectly(scope.row.useDate) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="使用原因" align="center" prop="useReason" />
+      <el-table-column label="计量项目" align="center" prop="measureProject" />
+      <el-table-column label="使用人" align="center" prop="usePerson" />
+
+      <el-table-column label="部门名称" align="center" prop="deptName" />
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['rq:iot-measure-record:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['rq:iot-measure-record: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>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <IotMeasureRecordForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import download from '@/utils/download'
+import { IotMeasureRecordApi, IotMeasureRecordVO } from '@/api/pms/qhse/index'
+import IotMeasureRecordForm from './IotMeasureRecordForm.vue'
+import { formatDate } from '@/utils/formatTime'
+
+/** 计量器具-使用记录 列表 */
+defineOptions({ name: 'IotMeasureRecord' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<IotMeasureRecordVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  measureName: undefined,
+  useDate: [],
+  useReason: undefined,
+  measureProject: undefined,
+  usePerson: undefined,
+  createTime: [],
+  deptId: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+// 正确格式化日期的函数
+const formatDateCorrectly = (timestamp) => {
+  if (!timestamp) return ''
+
+  // 如果是秒级时间戳,转换为毫秒级
+  let time = Number(timestamp)
+  if (time < 10000000000) {
+    // 小于这个数通常表示秒级时间戳
+    time = time * 1000
+  }
+
+  return formatDate(time).substring(0, 10)
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotMeasureRecordApi.getIotMeasureRecordPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotMeasureRecordApi.deleteIotMeasureRecord(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotMeasureRecordApi.exportIotMeasureRecord(queryParams)
+    download.excel(data, '计量器具-使用记录.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  getList()
+})
+</script>