yanghao 3 일 전
부모
커밋
ac390d7324

+ 1 - 1
.env.local

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

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

@@ -186,3 +186,28 @@ export const IotDangerApi = {
     return await request.download({ url: `/rq/iot-danger-source/export-excel`, params })
   }
 }
+
+// 环境因素识别
+export const IotEnvironmentApi = {
+  // 获得环境因素分页
+  getEnvironmentList: async (params) => {
+    return await request.get({ url: `/rq/iot-environment-recognize/page`, params })
+  },
+  // 删除环境因素
+  deleteEnvironment: async (id) => {
+    return await request.delete({ url: `/rq/iot-environment-recognize/delete?id=` + id })
+  },
+
+  // 添加环境因素
+  createEnvironment: async (data) => {
+    return await request.post({ url: `/rq/iot-environment-recognize/create`, data })
+  },
+  // 修改环境因素
+  updateEnvironment: async (data) => {
+    return await request.put({ url: `/rq/iot-environment-recognize/update`, data })
+  },
+  // 导出环境因素 Excel
+  exportEnvironment: async (params) => {
+    return await request.download({ url: `/rq/iot-environment-recognize/export-excel`, params })
+  }
+}

+ 9 - 0
src/views/pms/device/completeSet/DeviceCompleteSet.vue

@@ -64,6 +64,15 @@
             </template>
           </el-table-column>
 
+          <el-table-column label="在线状态" align="center">
+            <template #default="scope">
+              <el-tag type="success" :underline="false" v-if="scope.row.ifOnline === true"
+                >在线</el-tag
+              >
+              <el-tag type="info" :underline="false" v-else>离线</el-tag>
+            </template>
+          </el-table-column>
+
           <el-table-column label="描述" align="center" prop="remark" />
           <el-table-column label="设备数量" align="center" prop="deviceCount">
             <template #default="scope">

+ 41 - 8
src/views/pms/monitor/index.vue

@@ -56,18 +56,31 @@
                       <div class="card-title">{{ item.name }}</div>
                       <div class="card-dept">{{ item.deptName }}</div>
                     </div>
-                    <el-image
-                      v-if="item.type === '1'"
-                      :src="img1"
-                      style="width: 40px; height: 40px"
-                    />
-                    <el-image v-else :src="img2" style="width: 40px; height: 40px" />
+                    <el-tag
+                      :type="getOnlineStatusTagType(item.ifOnline)"
+                      size="small"
+                      class="status-tag"
+                    >
+                      {{ item.ifOnline ? '在线' : '离线' }}
+                    </el-tag>
                   </div>
                 </div>
-                <div class="card-body">
+                <div class="card-body" style="margin-top: -20px">
                   <div class="card-row">
                     <span class="muted">成套类型:</span>
-                    <dict-tag :type="DICT_TYPE.DEVICE_GROUP_TYPE" :value="item.type" />
+                    <span class="type-with-icon">
+                      <dict-tag :type="DICT_TYPE.DEVICE_GROUP_TYPE" :value="item.type" />
+                      <el-image
+                        v-if="item.type === '1'"
+                        :src="img1"
+                        style="width: 40px; height: 40px; margin-left: 40px"
+                      />
+                      <el-image
+                        v-else
+                        :src="img2"
+                        style="width: 40px; height: 40px; margin-left: 40px"
+                      />
+                    </span>
                   </div>
                   <div class="card-row"
                     ><span class="muted">设备数量:</span>
@@ -83,6 +96,12 @@
                         : '无'
                     }}
                   </div>
+                  <div class="card-row">
+                    <span class="muted">在线状态:</span>
+                    <el-tag :type="getOnlineStatusTagType(item.ifOnline)" size="small">
+                      {{ item.ifOnline ? '在线' : '离线' }}
+                    </el-tag>
+                  </div>
                   <!-- <div class="card-row"><span class="muted">描述:</span> {{ item.remark }}</div> -->
                 </div>
                 <template #footer>
@@ -155,6 +174,11 @@ const formData = ref({
   remark: ''
 })
 
+// 获取在线状态的标签类型
+const getOnlineStatusTagType = (ifOnline: boolean) => {
+  return ifOnline ? 'success' : 'info'
+}
+
 // 表单验证规则
 const formRules = {
   name: [{ required: true, message: '成套名称不能为空', trigger: 'blur' }],
@@ -473,6 +497,10 @@ onMounted(async () => {
   color: #34475c;
 }
 
+.device-card .status-tag {
+  margin-bottom: 8px;
+}
+
 .device-card .card-status {
   background: rgba(255, 255, 255, 0.18);
   color: #fff;
@@ -504,6 +532,11 @@ onMounted(async () => {
   flex-shrink: 0;
 }
 
+.device-card .type-with-icon {
+  display: inline-flex;
+  align-items: center;
+}
+
 .device-card .card-row .value {
   color: #fff;
   flex: 1;

+ 12 - 15
src/views/pms/qhse/certificate.vue

@@ -15,6 +15,7 @@
             <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>
 
@@ -152,10 +153,12 @@
         >
           <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">
+      <span class="absolute left-16 text-red" v-if="formData.type !== 'other'">*</span>
+      <el-form-item label="证书类别" prop="classify" v-show="formData.type !== 'other'">
         <el-select
           v-if="formData.type === 'personal'"
           v-model="formData.classify"
@@ -193,7 +196,6 @@
         />
       </el-form-item>
 
-      <span class="absolute left-19 text-red" v-if="formData.type === 'personal'">*</span>
       <el-form-item label="所属人" prop="userId">
         <el-select v-model="formData.userId" placeholder="请选择所属人" clearable>
           <el-option
@@ -361,31 +363,26 @@ const formatDateCorrectly = (timestamp) => {
 // 表单验证规则
 const formRules = {
   type: [{ required: true, message: '证书类型不能为空', trigger: 'blur' }],
-  classify: [{ required: true, message: '证书类别不能为空', trigger: 'blur' }],
-  deptId: [{ required: true, message: '所在部门不能为空', trigger: 'blur' }],
-  userId: [
+  classify: [
     {
-      required: false, // 默认不强制验证
+      required: false, // 默认设为非必填
       validator: (rule, value, callback) => {
-        // 只有当证书类型为个人且证书类别为个人证书时才验证
-        if (
-          formData.value.type === 'personal' &&
-          getStrDictOptions(DICT_TYPE.PERSON_CERT).some(
-            (personCert) => personCert.value === formData.value.classify
-          )
-        ) {
+        // 只有当证书类型不是 "other" 时才验证
+        if (formData.value.type !== 'other') {
           if (!value) {
-            callback(new Error('个人证书必须选择所属人'))
+            callback(new Error('证书类别不能为空'))
           } else {
             callback()
           }
         } else {
-          callback() // 不需要验证时直接通过
+          callback() // other 类型时不需要验证
         }
       },
       trigger: ['blur', 'change']
     }
   ],
+  deptId: [{ required: true, message: '所在部门不能为空', trigger: 'blur' }],
+
   certOrg: [{ required: true, message: '颁发机构不能为空', trigger: 'blur' }],
   certIssue: [{ required: true, message: '颁发时间不能为空', trigger: 'blur' }],
   certExpire: [{ required: true, message: '有效期不能为空', trigger: 'blur' }]

+ 650 - 0
src/views/pms/qhse/factor/index.vue

@@ -0,0 +1,650 @@
+<template>
+  <div class="factor-matrix">
+    <!-- 筛选表单 -->
+    <ContentWrap style="border: 0">
+      <el-form
+        class="pt-2"
+        :model="queryParams"
+        ref="queryFormRef"
+        :inline="true"
+        label-width="80px"
+      >
+        <el-form-item label="工序" prop="process">
+          <el-input
+            v-model="queryParams.process"
+            placeholder="请输入工序"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-200px"
+          />
+        </el-form-item>
+        <el-form-item label="步骤分解" prop="stepBreak">
+          <el-input
+            v-model="queryParams.stepBreak"
+            placeholder="请输入步骤分解"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-200px"
+          />
+        </el-form-item>
+        <el-form-item label="环境因素" prop="environmentElement">
+          <el-input
+            v-model="queryParams.environmentElement"
+            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" />搜索 </el-button>
+          <el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" />重置 </el-button>
+          <el-button type="primary" @click="openAddDialog" color="#626aef"
+            ><Icon icon="ep:plus" class="mr-5px" />新增</el-button
+          >
+          <el-button type="success" plain @click="handleExport" :loading="exportLoading">
+            <Icon icon="ep:download" class="mr-5px" /> 导出
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+
+    <!-- 表格 -->
+    <ContentWrap style="border: 0">
+      <el-table
+        :data="tableData"
+        border
+        stripe
+        style="width: 100%"
+        :header-cell-style="{ background: '#f5f7fa', color: '#333' }"
+        :cell-style="{ padding: '12px 8px' }"
+        height="68vh"
+      >
+        <el-table-column label="序号" width="70" align="center" fixed="left">
+          <template #default="scope">
+            {{ scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <el-table-column label="工序" prop="process" width="140" align="center" fixed="left" />
+        <el-table-column label="步骤分解" prop="stepBreak" width="140" align="center" />
+        <el-table-column label="环境因素" prop="environmentElement" width="180" align="center" />
+
+        <el-table-column label="时态" width="240" align="center">
+          <el-table-column label="过去" prop="timeBefore" width="80" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.timeBefore">
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="现在" prop="timeNow" width="80" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.timeNow">
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="将来" prop="timeFuture" width="80" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.timeFuture">
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+        </el-table-column>
+
+        <el-table-column label="状态" width="240" align="center">
+          <el-table-column label="正常" prop="statusNormal" width="80" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.statusNormal">
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="异常" prop="statusException" width="80" align="center">
+            <template #default="{ row }">
+              <el-button
+                circle
+                type="success"
+                style="border: none"
+                plain
+                v-if="row.statusException"
+              >
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="紧急" prop="statusDanger" width="80" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.statusDanger">
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+        </el-table-column>
+
+        <el-table-column label="环境影响类型" width="700" align="center">
+          <el-table-column label="能源/资源耗用" prop="typeEnergy" width="100" align="center">
+            <template #default="{ row }">
+              <!-- <span>
+                {{ row.typeEnergy ? '✔' : '' }}
+              </span> -->
+
+              <el-button circle type="success" style="border: none" plain v-if="row.typeEnergy">
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="水体" prop="typeWater" width="100" align="center">
+            <template #default="{ row }">
+              <!-- <span>
+                {{ row.typeWater ? '✔' : '' }}
+              </span> -->
+              <el-button circle type="success" style="border: none" plain v-if="row.typeWater">
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="大气" prop="typeGas" width="100" align="center">
+            <template #default="{ row }">
+              <!-- <span>
+                {{ row.typeGas ? '✔' : '' }}
+              </span> -->
+
+              <el-button circle type="success" style="border: none" plain v-if="row.typeGas">
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="噪音" prop="typeNoise" width="100" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.typeNoise">
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="废弃物" prop="typeWaste" width="100" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.typeWaste">
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="土壤" prop="typeSoil" width="100" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.typeSoil">
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="其他" prop="typeOther" width="100" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.typeOther">
+                {{ '✔' }}
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+        </el-table-column>
+
+        <el-table-column label="控制措施" prop="controlMethod" min-width="200" align="center" />
+        <el-table-column label="创建日期" prop="createTime" width="160" align="center">
+          <template #default="{ row }">
+            {{ formatDate(row.createTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" prop="remark" width="150" align="center" />
+
+        <el-table-column label="操作" width="120" fixed="right" align="center">
+          <template #default="{ row }">
+            <el-button type="primary" link @click="editRow(row)">编辑</el-button>
+            <el-button type="danger" link @click="deleteRow(row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <div class="mt-2 flex justify-right">
+        <el-pagination
+          v-model:current-page="pagination.pageNo"
+          v-model:page-size="pagination.pageSize"
+          :total="total"
+          layout="total, sizes, prev, pager, next"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+          background
+        />
+      </div>
+    </ContentWrap>
+
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog :title="dialogTitle" v-model="dialogVisible" width="50%" destroy-on-close>
+      <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="process">
+              <el-input v-model="formData.process" placeholder="请输入工序" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="步骤分解" prop="stepBreak">
+              <el-input v-model="formData.stepBreak" placeholder="请输入步骤分解" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="环境因素" prop="environmentElement">
+              <el-input v-model="formData.environmentElement" placeholder="请输入环境因素" />
+            </el-form-item>
+          </el-col>
+          <!-- <el-col :span="12">
+            <el-form-item label="控制措施" prop="controlMethod">
+              <el-input
+                type="textarea"
+                v-model="formData.controlMethod"
+                placeholder="请输入控制措施"
+              />
+            </el-form-item>
+          </el-col> -->
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="时态" prop="timeValue">
+              <el-radio-group v-model="formData.timeValue">
+                <el-radio label="timeBefore">过去</el-radio>
+                <el-radio label="timeNow">现在</el-radio>
+                <el-radio label="timeFuture">将来</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="状态" prop="statusValue">
+              <el-radio-group v-model="formData.statusValue">
+                <el-radio label="statusNormal">正常</el-radio>
+                <el-radio label="statusException">异常</el-radio>
+                <el-radio label="statusDanger">紧急</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="环境影响类型" prop="typeValue">
+              <el-radio-group v-model="formData.typeValue">
+                <el-radio label="typeEnergy">能源/资源耗用</el-radio>
+                <el-radio label="typeWater">水体污染</el-radio>
+                <el-radio label="typeGas">大气污染</el-radio>
+                <el-radio label="typeNoise">噪声污染</el-radio>
+                <el-radio label="typeWaste">废弃物</el-radio>
+                <el-radio label="typeSoil">土壤污染</el-radio>
+                <el-radio label="typeOther">其他</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="控制措施" prop="controlMethod">
+              <el-input
+                type="textarea"
+                v-model="formData.controlMethod"
+                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="备注">
+              <el-input v-model="formData.remark" placeholder="请输入备注" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="cancelForm">取 消</el-button>
+        <el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, nextTick } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { IotEnvironmentApi } from '@/api/pms/qhse/index'
+import { formatDate } from '@/utils/formatTime'
+import { defaultProps } from '@/utils/tree'
+import { handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+
+// 表格数据
+const deptList2 = ref<Tree[]>([]) // 树形结构
+const tableData = ref([])
+const total = ref(0)
+
+// 筛选参数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  process: '',
+  stepBreak: '',
+  environmentElement: ''
+})
+
+const pagination = reactive({
+  pageNo: 1,
+  pageSize: 10
+})
+
+// 分页和查询
+const queryFormRef = ref()
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+const handleSizeChange = (val) => {
+  queryParams.pageSize = val
+  queryParams.pageNo = 1 // 重置为第一页
+
+  getList()
+}
+
+const handleCurrentChange = (val) => {
+  queryParams.pageNo = val
+  getList()
+}
+
+const downloadFile = (response) => {
+  // 创建 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 exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    exportLoading.value = true
+    // 调用导出接口
+    const response = await IotEnvironmentApi.exportEnvironment(queryParams)
+
+    // 下载文件
+    downloadFile(response)
+    exportLoading.value = false
+  } catch (error) {
+    ElMessage.error('导出失败,请重试')
+    console.error('导出错误:', error)
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+const getList = async () => {
+  const res = await IotEnvironmentApi.getEnvironmentList(queryParams)
+  tableData.value = res.list
+  total.value = res.total
+}
+
+// 弹窗相关
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const formLoading = ref(false)
+const submitLoading = ref(false)
+const isEdit = ref(false)
+
+// 表单数据
+const formData = ref({
+  process: '',
+  stepBreak: '',
+  environmentElement: '',
+  timeValue: '', // 单选时态值
+  statusValue: '', // 单选状态值
+  typeValue: '', // 单选影响类型值
+  controlMethod: '',
+  remark: '',
+  deptId: null as number | null
+})
+
+// 表单验证规则
+const formRules = {
+  stepBreak: [{ required: true, message: '请输入步骤分解', trigger: 'blur' }],
+  environmentElement: [{ required: true, message: '请输入环境因素', trigger: 'blur' }],
+  controlMethod: [{ required: true, message: '请输入控制措施', trigger: 'blur' }],
+  timeValue: [{ required: true, message: '请选择时态', trigger: 'change' }],
+  statusValue: [{ required: true, message: '请选择状态', trigger: 'change' }],
+  typeValue: [{ required: true, message: '请选择环境影响类型', trigger: 'change' }]
+}
+
+// 表单引用
+const formRef = ref()
+
+// 打开新增对话框
+const openAddDialog = () => {
+  isEdit.value = false
+  dialogTitle.value = '新增环境因素'
+  resetFormData()
+  dialogVisible.value = true
+}
+
+// 编辑行
+const editRow = (row) => {
+  isEdit.value = true
+  dialogTitle.value = '编辑环境因素'
+
+  // 构造表单数据
+  formData.value = {
+    ...row,
+    timeValue: '', // 初始化单选值
+    statusValue: '', // 初始化单选值
+    typeValue: '' // 初始化单选值
+  }
+
+  // 设置单选值
+  if (row.timeBefore) formData.value.timeValue = 'timeBefore'
+  if (row.timeNow) formData.value.timeValue = 'timeNow'
+  if (row.timeFuture) formData.value.timeValue = 'timeFuture'
+
+  if (row.statusNormal) formData.value.statusValue = 'statusNormal'
+  if (row.statusException) formData.value.statusValue = 'statusException'
+  if (row.statusDanger) formData.value.statusValue = 'statusDanger'
+
+  if (row.typeEnergy) formData.value.typeValue = 'typeEnergy'
+  if (row.typeWater) formData.value.typeValue = 'typeWater'
+  if (row.typeGas) formData.value.typeValue = 'typeGas'
+  if (row.typeNoise) formData.value.typeValue = 'typeNoise'
+  if (row.typeWaste) formData.value.typeValue = 'typeWaste'
+  if (row.typeSoil) formData.value.typeValue = 'typeSoil'
+  if (row.typeOther) formData.value.typeValue = 'typeOther'
+
+  dialogVisible.value = true
+}
+
+// 删除行
+const deleteRow = (row) => {
+  ElMessageBox.confirm('确定要删除这条记录吗?', '警告', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      // 这里应该调用实际的API删除
+      await IotEnvironmentApi.deleteEnvironment(row.id)
+
+      ElMessage.success('删除成功')
+      getList() // 重新获取列表
+    })
+    .catch(() => {
+      // 取消删除
+    })
+}
+
+// 重置表单数据
+const resetFormData = () => {
+  formData.value = {
+    process: '',
+    stepBreak: '',
+    environmentElement: '',
+    timeValue: '',
+    statusValue: '',
+    typeValue: '',
+    controlMethod: '',
+    remark: '',
+    deptId: null
+  }
+  nextTick(() => {
+    formRef.value?.clearValidate()
+  })
+}
+
+// 取消表单
+const cancelForm = () => {
+  dialogVisible.value = false
+  resetFormData()
+}
+
+// 提交表单
+const submitForm = async () => {
+  if (!formRef.value) return
+
+  try {
+    await formRef.value.validate()
+    submitLoading.value = true
+
+    // 根据单选值更新布尔值
+    const updatedData = {
+      ...formData.value,
+      timeBefore: formData.value.timeValue === 'timeBefore',
+      timeNow: formData.value.timeValue === 'timeNow',
+      timeFuture: formData.value.timeValue === 'timeFuture',
+      statusNormal: formData.value.statusValue === 'statusNormal',
+      statusException: formData.value.statusValue === 'statusException',
+      statusDanger: formData.value.statusValue === 'statusDanger',
+      typeEnergy: formData.value.typeValue === 'typeEnergy',
+      typeWater: formData.value.typeValue === 'typeWater',
+      typeGas: formData.value.typeValue === 'typeGas',
+      typeNoise: formData.value.typeValue === 'typeNoise',
+      typeWaste: formData.value.typeValue === 'typeWaste',
+      typeSoil: formData.value.typeValue === 'typeSoil',
+      typeOther: formData.value.typeValue === 'typeOther'
+    }
+
+    // 删除临时单选字段
+    delete updatedData.timeValue
+    delete updatedData.statusValue
+    delete updatedData.typeValue
+
+    if (isEdit.value) {
+      // 更新操作
+      await IotEnvironmentApi.updateEnvironment(updatedData)
+      ElMessage.success('更新成功')
+    } else {
+      // 创建操作
+      await IotEnvironmentApi.createEnvironment(updatedData)
+      ElMessage.success('创建成功')
+    }
+
+    dialogVisible.value = false
+    getList()
+  } catch (error) {
+    console.error('表单验证失败:', error)
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+onMounted(async () => {
+  // 初始化数据
+  getList()
+  deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+})
+</script>
+
+<style scoped>
+.factor-matrix .toolbar {
+  margin: 12px 0;
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
+
+.el-table .row-actions {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  margin-top: 6px;
+}
+
+.el-checkbox .el-checkbox__input {
+  margin-left: 6px;
+}
+</style>

+ 15 - 6
src/views/pms/qhse/hazard/index.vue

@@ -42,7 +42,7 @@
         height="70vh"
       >
         <!-- 区域/位置 列(已合并) -->
-        <el-table-column prop="region" label="区域/位置" width="150" align="center" />
+        <el-table-column prop="region" label="区域/位置" width="150" align="center" fixed="left" />
 
         <!-- 其他列保持不变 -->
         <el-table-column label="序号" width="70" align="center">
@@ -56,7 +56,7 @@
           width="200"
           align="center"
         />
-        <el-table-column prop="maybeResult" label="可导致的后果" align="center" />
+        <el-table-column prop="maybeResult" min-width="320" label="可导致的后果" align="center" />
 
         <!-- 风险评价列保持不变 -->
         <el-table-column label="风险评价" width="320" align="center">
@@ -95,7 +95,8 @@
           show-overflow-tooltip
           align="center"
         />
-        <el-table-column label="操作" width="150" align="center">
+        <el-table-column prop="charge" label="责任人 " min-width="100" align="center" />
+        <el-table-column label="操作" width="150" align="center" fixed="right">
           <template #default="{ row }">
             <div class="flex gap-3 justify-center">
               <el-link
@@ -130,7 +131,7 @@
 
     <!-- 新增/编辑弹窗 -->
     <!-- 新增/编辑弹窗 -->
-    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px" @close="resetForm">
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="50%" @close="resetForm">
       <el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
         <el-row :gutter="20">
           <!-- 第一行 -->
@@ -207,6 +208,14 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
+            <el-form-item label="责任人" prop="charge">
+              <el-input v-model="formData.charge" placeholder="请输入责任人" />
+            </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"
@@ -259,7 +268,7 @@ const dialogVisible = ref(false)
 const dialogTitle = ref('新增')
 const formData = reactive({
   region: '', // 区域/位置
-
+  charge: '',
   elementDescription: '', // 危害因素描述
   maybeResult: '', // 可能导致的后果
   evalKn: 1, // 可能性
@@ -273,7 +282,7 @@ const formData = reactive({
 // 表单校验规则
 const rules = {
   region: [{ required: true, message: '请输入区域/位置', trigger: 'blur' }],
-
+  charge: [{ required: true, message: '请输入责任人', trigger: 'blur' }],
   elementDescription: [{ required: true, message: '请输入危害因素描述', trigger: 'blur' }],
   maybeResult: [{ required: true, message: '请输入可能导致的后果', trigger: 'blur' }],
   evalKn: [{ required: true, message: '请输入风险评价可能性', trigger: 'change' }],

+ 17 - 29
src/views/pms/qhse/index.vue

@@ -67,16 +67,12 @@
               {{ formatDateCorrectly(scope.row.buyDate) }}
             </template>
           </el-table-column>
-          <el-table-column label="有效期" align="center" prop="validity">
+          <!-- <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> -->
+
           <el-table-column label="价格" align="center" prop="measurePrice">
             <template #default="scope">
               {{ scope.row.measurePrice }}
@@ -214,20 +210,14 @@
           </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 label="备注" prop="remark">
+            <el-input v-model="formData.remark" type="textarea" placeholder="请输入描述" />
           </el-form-item>
         </el-col>
       </el-row>
 
       <el-row :gutter="20">
-        <el-col :span="12">
+        <!-- <el-col :span="12">
           <el-form-item label="有效期" prop="validity">
             <el-date-picker
               v-model="formData.validity"
@@ -237,21 +227,21 @@
               style="width: 100%"
             />
           </el-form-item>
-        </el-col>
-        <el-col :span="12">
+        </el-col> -->
+        <!-- <el-col :span="12">
           <el-form-item label="证书编码" prop="measureCertNo">
             <el-input v-model="formData.measureCertNo" placeholder="请输入证书编码" />
           </el-form-item>
-        </el-col>
+        </el-col> -->
       </el-row>
 
-      <el-row :gutter="20">
+      <!-- <el-row :gutter="20">
         <el-col :span="12">
           <el-form-item label="备注" prop="remark">
             <el-input v-model="formData.remark" type="textarea" placeholder="请输入描述" />
           </el-form-item>
         </el-col>
-      </el-row>
+      </el-row> -->
     </el-form>
 
     <template #footer>
@@ -313,8 +303,8 @@ const formData = ref({
   remark: '',
   deptId: '',
   measureCode: '',
-  validity: null, // 有效期
-  lastTime: null, // 上次检验/校准日期
+  // validity: null, // 有效期
+
   measurePrice: 0, // 价格
   measureCertNo: ''
 })
@@ -438,8 +428,7 @@ const handleEdit = (row) => {
     ...row,
     // 确保日期字段正确处理
     buyDate: row.buyDate ? ensureMillisecondTimestamp(row.buyDate) : null,
-    validity: row.validity ? ensureMillisecondTimestamp(row.validity) : null,
-    lastTime: row.lastTime ? ensureMillisecondTimestamp(row.lastTime) : null
+    validity: row.validity ? ensureMillisecondTimestamp(row.validity) : null
   }
 
   dialogVisible.value = true
@@ -489,8 +478,8 @@ const resetForm = () => {
     remark: '',
     deptId: '',
     measureCode: '',
-    validity: null, // 有效期
-    lastTime: null, // 上次检验/校准日期
+    // validity: null, // 有效期
+
     measurePrice: 0, // 价格
     measureCertNo: ''
   }
@@ -516,8 +505,7 @@ const submitForm = async () => {
       ...formData.value,
       // 确保日期字段以正确的格式提交
       buyDate: formData.value.buyDate,
-      validity: formData.value.validity,
-      lastTime: formData.value.lastTime
+      validity: formData.value.validity
     }
 
     if (isEdit.value) {

+ 105 - 59
src/views/pms/qhse/iotmeasuredetect/IotMeasureDetectForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible">
+  <Dialog :title="dialogTitle" v-model="dialogVisible" width="50%">
     <el-form
       ref="formRef"
       :model="formData"
@@ -7,62 +7,102 @@
       label-width="120px"
       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="detectDate">
-        <el-date-picker
-          v-model="formData.detectDate"
-          type="date"
-          value-format="x"
-          placeholder="选择检测/校准日期"
-          style="width: 200px"
-        />
-      </el-form-item>
-      <el-form-item label="检测/校准机构" prop="detectOrg">
-        <el-input
-          v-model="formData.detectOrg"
-          placeholder="请输入检测/校准机构"
-          style="width: 200px"
-        />
-      </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="选择检测/校准有效期"
-          style="width: 200px"
-        />
-      </el-form-item>
-      <el-form-item label="校准金额" prop="detectAmount">
-        <el-input
-          v-model="formData.detectAmount"
-          placeholder="请输入校准金额"
-          style="width: 200px"
-        />
-      </el-form-item>
-      <el-form-item label="部门" prop="deptId">
-        <el-tree-select
-          style="width: 220px"
-          clearable
-          v-model="formData.deptId"
-          :data="deptList2"
-          :props="defaultProps"
-          check-strictly
-          node-key="id"
-          filterable
-          placeholder="请选择所在部门"
-        />
-      </el-form-item>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="计量器具" prop="measureId">
+            <el-input
+              v-model="formData.measureName"
+              disabled
+              placeholder="计量器具"
+              style="width: 300px"
+            >
+              <template #append>
+                <el-link @click="selectMeasure" :underline="false">选择</el-link>
+              </template>
+            </el-input>
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="证书编码" prop="measureCertNo">
+            <el-input v-model="formData.measureCertNo" placeholder="请输入证书编码" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="检测/校准日期" prop="detectDate">
+            <el-date-picker
+              v-model="formData.detectDate"
+              type="date"
+              value-format="x"
+              placeholder="选择检测/校准日期"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="检测/校准机构" prop="detectOrg">
+            <el-input v-model="formData.detectOrg" placeholder="请输入检测/校准机构" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="检测/校准标准" prop="detectStandard">
+            <el-input v-model="formData.detectStandard" placeholder="请输入检测/校准标准" />
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="检测/校准有效期" prop="validityPeriod">
+            <el-date-picker
+              v-model="formData.validityPeriod"
+              type="date"
+              value-format="x"
+              placeholder="选择检测/校准有效期"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="校准金额" prop="detectAmount">
+            <el-input v-model="formData.detectAmount" placeholder="请输入校准金额" />
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="部门" prop="deptId">
+            <el-tree-select
+              style="width: 100%"
+              clearable
+              v-model="formData.deptId"
+              :data="deptList2"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              filterable
+              placeholder="请选择所在部门"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="检测/校准内容" prop="detectContent">
+            <Editor v-model="formData.detectContent" height="150px" />
+          </el-form-item>
+        </el-col>
+      </el-row>
     </el-form>
+
     <template #footer>
       <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
       <el-button @click="dialogVisible = false">取 消</el-button>
@@ -182,17 +222,21 @@ const formData = ref({
   detectDate: undefined,
   detectOrg: undefined,
   detectContent: undefined,
+  detectStandard: undefined,
   validityPeriod: undefined,
   detectAmount: undefined,
   deptId: undefined,
   measureName: '',
-  measureId: ''
+  measureId: '',
+  measureCertNo: ''
 })
 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' }]
+  validityPeriod: [{ required: true, message: '检测/校准有效期不能为空', trigger: 'blur' }],
+  measureCertNo: [{ required: true, message: '证书编码不能为空', trigger: 'blur' }],
+  detectStandard: [{ required: true, message: '检测/校准标准不能为空', trigger: 'blur' }]
 })
 const formRef = ref() // 表单 Ref
 const measureList = ref([])
@@ -277,7 +321,9 @@ const resetForm = () => {
     detectAmount: undefined,
     deptId: undefined,
     measureName: '',
-    measureId: ''
+    measureId: '',
+    measureCertNo: '',
+    detectStandard: undefined
   }
   formRef.value?.resetFields()
 }

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

@@ -59,12 +59,14 @@
   <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="measureCertNo" />
       <el-table-column label="检测/校准日期" align="center" prop="detectDate">
         <template #default="scope">
           <span>{{ formatDateCorrectly(scope.row.detectDate) }}</span>
         </template>
       </el-table-column>
       <el-table-column label="检测/校准机构" align="center" prop="detectOrg" />
+      <el-table-column label="检测/校准标准" align="center" prop="detectStandard" />
       <el-table-column label="检测/校准内容" align="center" prop="detectContent">
         <template #default="scope">
           <div v-html="scope.row.detectContent"></div>