Ver Fonte

Merge branch 'master' of http://1.94.244.160:3000/shuzhihua/pms-iot-vue

Zimo há 1 dia atrás
pai
commit
19ec5fabf3

+ 3 - 1
src/views/pms/qhse/MeasureCertDrawer.vue

@@ -156,6 +156,7 @@ type MeasureRow = {
   measureName?: string
   measureCode?: string
   deptId?: number | string
+  lockMeasureSelect?: boolean
 }
 
 const drawerVisible = ref(false)
@@ -205,7 +206,8 @@ const handleCreate = () => {
   formRef.value?.open('create', undefined, {
     measureId: currentMeasure.value.id,
     measureName: currentMeasure.value.measureName || currentMeasure.value.measureCode || '',
-    deptId: currentMeasure.value.deptId
+    deptId: currentMeasure.value.deptId,
+    lockMeasureSelect: true
   })
 }
 

+ 49 - 12
src/views/pms/qhse/certPerson/CertPersonForm.vue

@@ -15,6 +15,7 @@
           :check-strictly="false"
           node-key="id"
           filterable
+          @node-click="handleDeptTreeNodeClick"
           placeholder="请选择所在部门" />
       </el-form-item>
       <el-form-item label="姓名" prop="nickname">
@@ -138,6 +139,39 @@
             </el-radio>
           </el-radio-group>
         </el-form-item>
+
+        <el-form-item label="国际井控证" prop="gjjk">
+          <el-radio-group v-model="formData.gjjk">
+            <el-radio
+              v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+              :key="dict.value"
+              :value="dict.value">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item label="消防证" prop="xiaofang">
+          <el-radio-group v-model="formData.xiaofang">
+            <el-radio
+              v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+              :key="dict.value"
+              :value="dict.value">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item label="急救证" prop="jijiu">
+          <el-radio-group v-model="formData.jijiu">
+            <el-radio
+              v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+              :key="dict.value"
+              :value="dict.value">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
       </div>
     </el-form>
     <template #footer>
@@ -182,7 +216,10 @@ const formData = ref({
   dg: undefined,
   lxygzc: undefined,
   dz: undefined,
-  aqgl: undefined
+  aqgl: undefined,
+  gjjk: undefined,
+  xiaofang: undefined,
+  jijiu: undefined
 })
 const formRules = reactive({
   nickname: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
@@ -198,10 +235,19 @@ const formRules = reactive({
   dg: [{ required: true, message: '请选择', trigger: 'blur' }],
   lxygzc: [{ required: true, message: '请选择', trigger: 'blur' }],
   dz: [{ required: true, message: '请选择', trigger: 'blur' }],
-  aqgl: [{ required: true, message: '请选择', trigger: 'blur' }]
+  aqgl: [{ required: true, message: '请选择', trigger: 'blur' }],
+  gjjk: [{ required: true, message: '请选择', trigger: 'blur' }],
+  xiaofang: [{ required: true, message: '请选择', trigger: 'blur' }],
+  jijiu: [{ required: true, message: '请选择', trigger: 'blur' }]
 })
 const formRef = ref() // 表单 Ref
 
+const handleDeptTreeNodeClick = (data: Tree) => {
+  console.log(data)
+
+  formData.value.deptId = data.id
+}
+
 /** 打开弹窗 */
 const open = async (type: string, id?: number) => {
   dialogVisible.value = true
@@ -225,15 +271,6 @@ const open = async (type: string, id?: number) => {
 }
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
-// 手机号格式校验(不阻止提交)
-const validateMobileFormat = () => {
-  if (formData.value.mobile && !/^1[3-9]\d{9}$/.test(formData.value.mobile)) {
-    message.warning('手机号格式不正确,但不影响提交')
-    return false
-  }
-  return true
-}
-
 /** 提交表单 */
 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
 const submitForm = async () => {
@@ -258,7 +295,7 @@ const submitForm = async () => {
     const data = { ...formData.value }
 
     if (formType.value === 'create') {
-      data.deptId = userStore.getUser.deptId
+      // data.deptId = userStore.getUser.deptId
       await CertPersonApi.createCertPerson(data)
       message.success(t('common.createSuccess'))
     } else {

+ 33 - 0
src/views/pms/qhse/certPerson/index.vue

@@ -189,6 +189,39 @@
             </template>
           </zm-table-column>
 
+          <zm-table-column label="国际井控证" align="center">
+            <template #default="scope">
+              <el-button circle type="success" style="border: none" plain v-if="scope.row.gjjk">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </zm-table-column>
+
+          <zm-table-column label="消防证" align="center">
+            <template #default="scope">
+              <el-button circle type="success" style="border: none" plain v-if="scope.row.xiaofang">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </zm-table-column>
+
+          <zm-table-column label="急救证" align="center">
+            <template #default="scope">
+              <el-button circle type="success" style="border: none" plain v-if="scope.row.jijiu">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </zm-table-column>
+
           <zm-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
 
           <zm-table-column label="操作" align="center" min-width="120px" action fixed="right">

+ 1 - 0
src/views/pms/qhse/deviceCert/DeviceCertForm.vue

@@ -396,6 +396,7 @@ const confirmSelectDevice = () => {
   formData.value.deviceId = selectedDevice.value.id
   formData.value.deviceName = selectedDevice.value.deviceName
   formData.value.deptId = selectedDevice.value.deptId
+  formData.value.deviceCode = selectedDevice.value.deviceCode
   closeDeviceDialog()
 }
 

+ 46 - 32
src/views/pms/qhse/index.vue

@@ -134,6 +134,7 @@
               </zm-table-column>
               <zm-table-column label="名称" align="center" prop="measureName" fixed="left" />
               <zm-table-column label="编码" align="center" prop="measureCode" fixed="left" />
+              <zm-table-column label="序列号" align="center" prop="serialNo" fixed="left" />
               <zm-table-column label="部门名称" align="center" prop="deptName" />
               <zm-table-column label="计量单位" align="center" prop="measureUnit" />
               <zm-table-column label="责任人" align="center" prop="dutyPerson" />
@@ -215,17 +216,23 @@
       :model="formData"
       :rules="formRules"
       v-loading="formLoading"
-      label-width="120px">
+      label-width="auto">
       <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-col :span="12">
           <el-form-item label="责任人" prop="dutyPerson">
             <el-input v-model="formData.dutyPerson" placeholder="请输入责任人" />
           </el-form-item>
+        </el-col> -->
+
+        <el-col :span="12">
+          <el-form-item label="序列号" prop="serialNo">
+            <el-input v-model="formData.serialNo" placeholder="请输入序列号" />
+          </el-form-item>
         </el-col>
       </el-row>
 
@@ -243,27 +250,43 @@
               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-col :span="12">
           <el-form-item label="品牌" prop="brand">
             <el-input v-model="formData.brand" placeholder="请输入品牌" />
           </el-form-item>
-        </el-col>
+        </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-col :span="12">
+        <!-- <el-col :span="12">
           <el-form-item label="单位" prop="measureUnit">
             <el-input v-model="formData.measureUnit" placeholder="请输入单位" />
           </el-form-item>
-        </el-col>
+        </el-col> -->
       </el-row>
 
       <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="计量单位" prop="measureUnit">
+            <el-input v-model="formData.measureUnit" placeholder="请输入计量单位" />
+          </el-form-item>
+        </el-col>
         <el-col :span="12">
           <el-form-item label="分类" prop="classify">
             <el-select
@@ -279,7 +302,7 @@
             </el-select>
           </el-form-item>
         </el-col>
-        <el-col :span="12">
+        <!-- <el-col :span="12">
           <el-form-item label="价格" prop="measurePrice">
             <el-input-number
               v-model="formData.measurePrice"
@@ -288,10 +311,20 @@
               placeholder="请输入价格"
               style="width: 100%" />
           </el-form-item>
-        </el-col>
+        </el-col> -->
       </el-row>
 
       <el-row :gutter="20">
+        <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-col :span="12">
           <el-form-item label="采购日期" prop="buyDate">
             <el-date-picker
@@ -302,39 +335,20 @@
               style="width: 100%" />
           </el-form-item>
         </el-col>
-        <el-col :span="12">
+        <!-- <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 :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="measureCertNo">
-            <el-input v-model="formData.measureCertNo" placeholder="请输入证书编码" />
-          </el-form-item>
         </el-col> -->
       </el-row>
 
-      <!-- <el-row :gutter="20">
-        <el-col :span="12">
+      <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-row>
     </el-form>
 
     <template #footer>
@@ -531,7 +545,7 @@ const formData = ref({
   deptId: '',
   measureCode: '',
   // validity: null, // 有效期
-
+  serialNo: '',
   measurePrice: 0, // 价格
   measureCertNo: ''
 })
@@ -644,7 +658,7 @@ const handleAdd = () => {
   isEdit.value = false
   dialogTitle.value = '新增台账'
   resetForm()
-  formData.value.deptId = userStore.getUser.deptId // 默认选择当前用户的部门
+  formData.value.deptId = queryParams.deptId || userStore.getUser.deptId
   dialogVisible.value = true
 }
 

+ 16 - 2
src/views/pms/qhse/iotmeasuredetect/IotMeasureDetectForm.vue

@@ -15,7 +15,12 @@
               placeholder="计量器具"
               style="width: 300px">
               <template #append>
-                <el-link @click="selectMeasure" :underline="false">选择</el-link>
+                <el-link
+                  :disabled="measureSelectLocked"
+                  @click="selectMeasure"
+                  :underline="false">
+                  选择
+                </el-link>
               </template>
             </el-input>
           </el-form-item>
@@ -228,6 +233,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
 const dialogTitle = ref('') // 弹窗的标题
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const measureSelectLocked = ref(false)
 const formData = ref({
   detectDate: undefined,
   detectOrg: undefined,
@@ -258,12 +264,18 @@ const measureList = ref([])
 const open = async (
   type: string,
   id?: number,
-  presetData?: { measureId?: number | string; measureName?: string; deptId?: number | string }
+  presetData?: {
+    measureId?: number | string
+    measureName?: string
+    deptId?: number | string
+    lockMeasureSelect?: boolean
+  }
 ) => {
   dialogVisible.value = true
   dialogTitle.value = t('action.' + type)
   formType.value = type
   resetForm()
+  measureSelectLocked.value = !!presetData?.lockMeasureSelect
   if (type === 'create' && presetData) {
     formData.value.measureId = presetData.measureId || ''
     formData.value.measureName = presetData.measureName || ''
@@ -345,6 +357,7 @@ const formatDateCorrectly = (timestamp) => {
 
 /** 重置表单 */
 const resetForm = () => {
+  measureSelectLocked.value = false
   formData.value = {
     detectDate: undefined,
     detectOrg: undefined,
@@ -394,6 +407,7 @@ const handleRadioChange = (row: any) => {
   selectedMeasure.value = row
 }
 const selectMeasure = () => {
+  if (measureSelectLocked.value) return
   measureDialogVisible.value = true
   getList()
 

+ 6 - 5
src/views/pms/qhse/monthlyReport/MonthlyReportAdd.vue

@@ -416,7 +416,7 @@
             <span>其他信息</span>
           </div>
         </template>
-        <el-row :gutter="16">
+        <!-- <el-row :gutter="16">
           <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
             <el-form-item label="工单填报人" prop="dutyPerson">
               <el-select
@@ -433,7 +433,7 @@
               </el-select>
             </el-form-item>
           </el-col>
-        </el-row>
+        </el-row> -->
         <el-row :gutter="16">
           <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
             <el-form-item label="备注" prop="remark">
@@ -568,13 +568,14 @@ const handleSubmit = async () => {
   // 校验表单
   const valid = await formRef.value.validate()
   if (!valid) return
-
+  reportInfo.dutyPerson = userInfo.value.id
   submitLoading.value = true
   try {
     await QhseMonthReportApi.updateQhseMonthReport(reportInfo)
-    message.success('新增成功')
+    message.success('提交成功')
     // 返回列表页或上一页
-    router.back()
+    delView(unref(router.currentRoute))
+    router.push({ name: 'QhseMonthlyReport', params: {} })
   } catch (error) {
     console.error('提交失败:', error)
   } finally {

+ 265 - 0
src/views/pms/qhse/reportSummary/index.vue

@@ -0,0 +1,265 @@
+<script lang="ts" setup>
+import { QhseMonthReportApi } from '@/api/pms/qhse'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+import QhseMonthReportPreviewDrawer from './preview-drawer.vue'
+import type { QhseMonthReportItem, QhseMonthReportListItem } from './types'
+import dayjs from 'dayjs'
+
+const loading = ref(false)
+const list = ref<QhseMonthReportListItem[]>([])
+const total = ref(0)
+const visible = ref(false)
+const currentId = ref<number>()
+
+const queryFormRef = ref()
+const queryParams = reactive({
+  title: '',
+  createTime: undefined as string[] | undefined
+})
+const pagination = reactive({
+  pageNo: 1,
+  pageSize: 10
+})
+
+const { ZmTable, ZmTableColumn } = useTableComponents<QhseMonthReportListItem>()
+
+async function getList() {
+  loading.value = true
+  try {
+    const res = await QhseMonthReportApi.getQhseMonthReportPage({
+      ...queryParams,
+      pageNo: pagination.pageNo,
+      pageSize: pagination.pageSize
+    })
+    list.value = res?.list || []
+    total.value = res?.total || 0
+  } finally {
+    loading.value = false
+  }
+}
+
+function handleQuery() {
+  pagination.pageNo = 1
+  getList()
+}
+
+function resetQuery() {
+  queryFormRef.value?.resetFields()
+  pagination.pageNo = 1
+  getList()
+}
+
+function handleView(row: QhseMonthReportItem) {
+  currentId.value = row.id
+  visible.value = true
+}
+
+function handleSizeChange(pageSize: number) {
+  pagination.pageSize = pageSize
+  pagination.pageNo = 1
+  getList()
+}
+
+function handleCurrentChange(pageNo: number) {
+  pagination.pageNo = pageNo
+  getList()
+}
+
+function formatDate(value?: string | number | Date) {
+  if (!value) return '-'
+  const date = dayjs(value)
+  return date.isValid() ? date.format('YYYY-MM-DD') : '-'
+}
+
+onMounted(() => {
+  getList()
+})
+</script>
+
+<template>
+  <div
+    class="report-summary-page min-w-0 overflow-x-hidden grid grid-rows-[auto_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]">
+    <el-form
+      ref="queryFormRef"
+      :model="queryParams"
+      inline
+      label-position="left"
+      class="report-summary-query min-w-0 overflow-hidden rounded-lg bg-white p-4 shadow dark:bg-[#1d1e1f]">
+      <el-form-item label="月报标题" prop="title">
+        <el-input v-model="queryParams.title" placeholder="请输入月报标题" clearable />
+      </el-form-item>
+      <el-form-item label="创建日期" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          type="daterange"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          class="!w-300px" />
+      </el-form-item>
+      <el-form-item class="report-summary-query__actions">
+        <el-button type="primary" @click="handleQuery">搜索</el-button>
+        <el-button @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <div class="report-summary-data-panel bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col p-4 min-h-0">
+      <div class="flex-1 min-h-0 relative">
+        <el-auto-resizer class="report-summary-table-view absolute">
+          <template #default="{ width, height }">
+            <zm-table
+              :data="list"
+              :loading="loading"
+              :width="width"
+              :max-height="height"
+              :height="height"
+              show-border>
+              <ZmTableColumn label="月报标题" prop="title" :min-width="260" />
+              <ZmTableColumn label="工单填报人" prop="personName" width="160" />
+              <ZmTableColumn
+                label="创建日期"
+                prop="createTime"
+                cover-formatter
+                :real-value="({ createTime }) => formatDate(createTime)"
+                width="160" />
+              <ZmTableColumn label="操作" width="100" fixed="right">
+                <template #default="{ row }">
+                  <el-button size="default" link type="success" @click="handleView(row)">
+                    查看
+                  </el-button>
+                </template>
+              </ZmTableColumn>
+            </zm-table>
+          </template>
+        </el-auto-resizer>
+
+        <div v-loading="loading" class="report-summary-card-view">
+          <template v-if="list.length">
+            <article v-for="item in list" :key="item.id" class="report-summary-card">
+              <div class="report-summary-card__title">
+                <span>月报标题</span>
+                <strong>{{ item.title || '-' }}</strong>
+              </div>
+              <div class="report-summary-card__field">
+                <span>填报人</span>
+                <strong>{{ item.personName || '-' }}</strong>
+              </div>
+              <div class="report-summary-card__field">
+                <span>创建日期</span>
+                <strong>{{ formatDate(item.createTime) }}</strong>
+              </div>
+              <div class="report-summary-card__actions">
+                <el-button size="default" link type="success" @click="handleView(item)">
+                  查看
+                </el-button>
+              </div>
+            </article>
+          </template>
+          <el-empty v-else description="暂无数据" :image-size="80" />
+        </div>
+      </div>
+
+      <div class="mt-4 flex justify-end">
+        <el-pagination
+          v-model:current-page="pagination.pageNo"
+          v-model:page-size="pagination.pageSize"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          background
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange" />
+      </div>
+    </div>
+  </div>
+
+  <QhseMonthReportPreviewDrawer
+    v-model:visible="visible"
+    :id="currentId"
+    @update:visible="visible = $event" />
+</template>
+
+<style scoped lang="scss">
+.report-summary-query {
+  :deep(.el-form-item) {
+    margin-bottom: 0;
+  }
+}
+
+.report-summary-query__actions {
+  :deep(.el-form-item__content) {
+    gap: 12px;
+  }
+}
+
+.report-summary-table-view {
+  display: block;
+}
+
+.report-summary-card-view {
+  display: none;
+}
+
+.report-summary-card {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  padding: 14px;
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 10px;
+}
+
+.report-summary-card__title,
+.report-summary-card__field {
+  display: grid;
+  gap: 4px;
+}
+
+.report-summary-card__title span,
+.report-summary-card__field span {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.report-summary-card__title strong,
+.report-summary-card__field strong {
+  font-size: 14px;
+  line-height: 1.5;
+  color: var(--el-text-color-primary);
+  overflow-wrap: anywhere;
+}
+
+.report-summary-card__actions {
+  display: flex;
+  justify-content: flex-end;
+  padding-top: 4px;
+  border-top: 1px solid var(--el-border-color-lighter);
+}
+
+@media (width < 768px) {
+  .report-summary-page {
+    height: auto;
+    min-height: calc(
+      100vh - 20px - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
+    );
+    gap: 12px;
+  }
+
+  .report-summary-data-panel {
+    min-height: 280px;
+    padding: 12px;
+  }
+
+  .report-summary-table-view {
+    display: none;
+  }
+
+  .report-summary-card-view {
+    display: flex;
+    flex: 1;
+    flex-direction: column;
+    gap: 12px;
+    overflow-y: auto;
+  }
+}
+</style>

+ 444 - 0
src/views/pms/qhse/reportSummary/preview-drawer.vue

@@ -0,0 +1,444 @@
+<script lang="ts" setup>
+import { QhseMonthReportApi } from '@/api/pms/qhse'
+import type {
+  QhseMonthReportItem,
+  ReportCompanyColumn,
+  ReportMetricRow,
+  ReportMetricValue
+} from './types'
+import dayjs from 'dayjs'
+import { computed, ref, watch } from 'vue'
+
+interface Props {
+  visible: boolean
+  id?: number
+}
+
+interface TableRow {
+  category: string
+  label: string
+  field: keyof QhseMonthReportItem
+  summary: string
+  [key: string]: string | keyof QhseMonthReportItem
+}
+
+const props = defineProps<Props>()
+const emits = defineEmits(['update:visible'])
+
+const loading = ref(false)
+const report = ref<QhseMonthReportItem>()
+
+const companyColumns: ReportCompanyColumn[] = [
+  { key: 'rhxy', label: '瑞恒兴域' },
+  { key: 'scrd', label: '四川瑞都' },
+  { key: 'sxty', label: '陕西瑞鹰' },
+  { key: 'eys', label: '俄油服' },
+  { key: 'rqny', label: '瑞气能源' },
+  { key: 'rljs', label: '瑞霖技术' },
+  { key: 'bjhq', label: '北京总部' }
+]
+
+const metricRows: ReportMetricRow[] = [
+  { category: '人工时与里程', label: '员工人数', field: 'employee', unit: '人' },
+  { category: '人工时与里程', label: '分包商人数', field: 'subcontractors', unit: '人' },
+  {
+    category: '人工时与里程',
+    label: '安全行驶里程数(公里)',
+    field: 'drivingMileage',
+    unit: '公里'
+  },
+  { category: '人工时与里程', label: '总人工时数(小时)', field: 'totalManHours', unit: '小时' },
+  { category: '被动性指标', label: '无事故累计天数(天)', field: 'withoutAccident', unit: '天' },
+  { category: '被动性指标', label: '死亡事故(起)', field: 'fatality', unit: '起' },
+  { category: '被动性指标', label: '损失工时事故(起)', field: 'injury', unit: '起' },
+  { category: '被动性指标', label: '工作受限事件(起)', field: 'restrictedCase', unit: '起' },
+  { category: '被动性指标', label: '医疗处理事件(起)', field: 'medicalCase', unit: '起' },
+  { category: '被动性指标', label: '急救箱事件(起)', field: 'firstAidCase', unit: '起' },
+  { category: '被动性指标', label: '交通事故(起)', field: 'vehicleAccident', unit: '起' },
+  { category: '被动性指标', label: '未遂事件(起)', field: 'nearMiss', unit: '起' },
+  { category: '被动性指标', label: '泄漏事件(起)', field: 'spill', unit: '起' },
+  { category: '被动性指标', label: '违反保命规则的次数(次)', field: 'lifeSavingRules', unit: '次' },
+  { category: '主动性指标', label: '班前会(次)', field: 'toolboxTalk', unit: '次' },
+  { category: '主动性指标', label: 'QHSE管理委员会会议', field: 'committeeMeeting', unit: '次' },
+  { category: '主动性指标', label: 'QHSE月度例会', field: 'monthlyMeeting', unit: '次' },
+  { category: '主动性指标', label: '公司级隐患排查', field: 'companyHazard', unit: '次' },
+  { category: '主动性指标', label: 'QHSE检查', field: 'qhseInspection', unit: '次' },
+  { category: '主动性指标', label: '安全观察卡', field: 'socCards', unit: '张' },
+  { category: '主动性指标', label: '工作许可审核', field: 'ptwAudit', unit: '份' },
+  { category: '主动性指标', label: '工作安全分析', field: 'jsa', unit: '次' },
+  { category: '主动性指标', label: '演练次数', field: 'drills', unit: '次' },
+  { category: '主动性指标', label: 'QHSE培训次数', field: 'training', unit: '次' },
+  { category: '主动性指标', label: 'QHSE培训人次', field: 'participantsTraining', unit: '人次' },
+  { category: '主动性指标', label: 'QHSE培训学时数', field: 'trainingsHours', unit: '小时' },
+  { category: '环境数据', label: '水消耗', field: 'waterConsumption', unit: '吨' },
+  { category: '环境数据', label: '柴油消耗', field: 'dieselConsumption', unit: '升' },
+  { category: '环境数据', label: '用电量', field: 'electricityConsumption', unit: '千瓦·小时' },
+  { category: '环境数据', label: '天然气消耗量', field: 'naturalGasConsumption', unit: '立方米' },
+  { category: '其他信息', label: '备注', field: 'remark', unit: '/' }
+]
+
+const categoryRowSpanMap = computed(() => {
+  return metricRows.reduce<Record<string, number>>((acc, item) => {
+    acc[item.category] = (acc[item.category] || 0) + 1
+    return acc
+  }, {})
+})
+
+const firstRowIndexByCategory = computed(() => {
+  return metricRows.reduce<Record<string, number>>((acc, item, index) => {
+    if (acc[item.category] === undefined) acc[item.category] = index
+    return acc
+  }, {})
+})
+
+const mockMetricValueMap = computed<Record<string, Record<string, ReportMetricValue>>>(() => ({
+  employee: { rhxy: 32, scrd: 28, sxty: 25, eys: 18, rqny: 20, rljs: 14, bjhq: 12 },
+  subcontractors: { rhxy: 15, scrd: 12, sxty: 9, eys: 7, rqny: 6, rljs: 5, bjhq: 0 },
+  drivingMileage: {
+    rhxy: 12680.5,
+    scrd: 11024.2,
+    sxty: 9480.8,
+    eys: 6855.6,
+    rqny: 7742.4,
+    rljs: 4136.5,
+    bjhq: 980.2
+  },
+  totalManHours: {
+    rhxy: 3824,
+    scrd: 3416,
+    sxty: 2988,
+    eys: 2210,
+    rqny: 2456,
+    rljs: 1768,
+    bjhq: 960
+  },
+  withoutAccident: { rhxy: 186, scrd: 186, sxty: 186, eys: 132, rqny: 186, rljs: 186, bjhq: 186 },
+  fatality: { rhxy: 0, scrd: 0, sxty: 0, eys: 0, rqny: 0, rljs: 0, bjhq: 0 },
+  injury: { rhxy: 0, scrd: 1, sxty: 0, eys: 0, rqny: 0, rljs: 0, bjhq: 0 },
+  restrictedCase: { rhxy: 1, scrd: 0, sxty: 0, eys: 0, rqny: 1, rljs: 0, bjhq: 0 },
+  medicalCase: { rhxy: 1, scrd: 1, sxty: 0, eys: 0, rqny: 0, rljs: 0, bjhq: 0 },
+  firstAidCase: { rhxy: 2, scrd: 1, sxty: 1, eys: 0, rqny: 1, rljs: 0, bjhq: 0 },
+  vehicleAccident: { rhxy: 0, scrd: 0, sxty: 0, eys: 1, rqny: 0, rljs: 0, bjhq: 0 },
+  nearMiss: { rhxy: 3, scrd: 2, sxty: 1, eys: 1, rqny: 2, rljs: 1, bjhq: 0 },
+  spill: { rhxy: 0, scrd: 0, sxty: 0, eys: 0, rqny: 1, rljs: 0, bjhq: 0 },
+  lifeSavingRules: { rhxy: 0, scrd: 1, sxty: 0, eys: 0, rqny: 0, rljs: 0, bjhq: 0 },
+  toolboxTalk: { rhxy: 28, scrd: 24, sxty: 22, eys: 18, rqny: 20, rljs: 16, bjhq: 6 },
+  committeeMeeting: { rhxy: 1, scrd: 1, sxty: 1, eys: 1, rqny: 1, rljs: 1, bjhq: 1 },
+  monthlyMeeting: { rhxy: 1, scrd: 1, sxty: 1, eys: 1, rqny: 1, rljs: 1, bjhq: 1 },
+  companyHazard: { rhxy: 6, scrd: 5, sxty: 4, eys: 3, rqny: 4, rljs: 2, bjhq: 1 },
+  qhseInspection: { rhxy: 10, scrd: 9, sxty: 8, eys: 6, rqny: 7, rljs: 5, bjhq: 3 },
+  socCards: { rhxy: 42, scrd: 38, sxty: 31, eys: 22, rqny: 27, rljs: 18, bjhq: 12 },
+  ptwAudit: { rhxy: 18, scrd: 15, sxty: 13, eys: 8, rqny: 9, rljs: 6, bjhq: 2 },
+  jsa: { rhxy: 21, scrd: 18, sxty: 16, eys: 10, rqny: 12, rljs: 8, bjhq: 3 },
+  drills: { rhxy: 2, scrd: 2, sxty: 1, eys: 1, rqny: 1, rljs: 1, bjhq: 1 },
+  training: { rhxy: 5, scrd: 4, sxty: 4, eys: 3, rqny: 3, rljs: 2, bjhq: 2 },
+  participantsTraining: { rhxy: 96, scrd: 82, sxty: 74, eys: 48, rqny: 56, rljs: 35, bjhq: 24 },
+  trainingsHours: { rhxy: 64, scrd: 56, sxty: 48, eys: 32, rqny: 36, rljs: 24, bjhq: 16 },
+  waterConsumption: {
+    rhxy: 82.5,
+    scrd: 74.2,
+    sxty: 65.8,
+    eys: 48.6,
+    rqny: 53.4,
+    rljs: 31.8,
+    bjhq: 12.2
+  },
+  dieselConsumption: {
+    rhxy: 2680,
+    scrd: 2410,
+    sxty: 2085,
+    eys: 1530,
+    rqny: 1695,
+    rljs: 980,
+    bjhq: 220
+  },
+  electricityConsumption: {
+    rhxy: 4250,
+    scrd: 3980,
+    sxty: 3650,
+    eys: 2420,
+    rqny: 2860,
+    rljs: 1680,
+    bjhq: 920
+  },
+  naturalGasConsumption: {
+    rhxy: 1260,
+    scrd: 1140,
+    sxty: 980,
+    eys: 660,
+    rqny: 720,
+    rljs: 450,
+    bjhq: 180
+  },
+  remark: {
+    rhxy: '现场管理平稳',
+    scrd: '专项培训已完成',
+    sxty: '持续推进隐患整改',
+    eys: '强化车辆安全检查',
+    rqny: '开展环保专项复盘',
+    rljs: '重点盯控作业许可',
+    bjhq: '推进体系宣贯'
+  }
+}))
+
+const tableRows = computed<TableRow[]>(() => {
+  return metricRows.map((row) => {
+    const companyValues = Object.fromEntries(
+      companyColumns.map((company) => [company.key, getMetricCompanyValue(row.field, company.key)])
+    )
+
+    return {
+      category: row.category,
+      label: row.label,
+      field: row.field,
+      summary: getMetricSummaryValue(row.field),
+      ...companyValues
+    }
+  })
+})
+
+async function loadDetail(id: number) {
+  loading.value = true
+  try {
+    const res = await QhseMonthReportApi.getQhseMonthReport(id)
+    report.value = ((res as any)?.data ?? res ?? {}) as QhseMonthReportItem
+  } finally {
+    loading.value = false
+  }
+}
+
+function handleVisibleChange(visible: boolean) {
+  emits('update:visible', visible)
+  if (!visible) report.value = undefined
+}
+
+function formatDisplayValue(field: keyof QhseMonthReportItem) {
+  const value = report.value?.[field]
+  if (value === undefined || value === null || value === '') return '-'
+  if (field === 'createTime') {
+    const date = dayjs(value)
+    return date.isValid() ? date.format('YYYY-MM-DD') : String(value)
+  }
+  return String(value)
+}
+
+function getMetricCompanyValue(field: keyof QhseMonthReportItem, companyKey: string) {
+  const rowData = mockMetricValueMap.value[String(field)] || {}
+  const value = rowData[companyKey]
+  if (value === undefined || value === null || value === '') return '-'
+  if (typeof value === 'number' && !Number.isInteger(value)) return value.toFixed(2)
+  return String(value)
+}
+
+function getMetricSummaryValue(field: keyof QhseMonthReportItem) {
+  const rowData = mockMetricValueMap.value[String(field)] || {}
+  const values = companyColumns
+    .map((company) => rowData[company.key])
+    .filter((value) => value !== undefined && value !== null && value !== '')
+
+  if (!values.length) return '-'
+  if (values.every((value) => typeof value === 'number')) {
+    const total = values.reduce((sum, value) => sum + Number(value), 0)
+    return Number.isInteger(total) ? String(total) : total.toFixed(2)
+  }
+  return values.map((value) => String(value)).join(';')
+}
+
+function tableSpanMethod({
+  row,
+  column,
+  rowIndex
+}: {
+  row: TableRow
+  column: { property?: string }
+  rowIndex: number
+}) {
+  if (column.property !== 'category') return { rowspan: 1, colspan: 1 }
+  const firstIndex = firstRowIndexByCategory.value[row.category]
+  if (firstIndex !== rowIndex) return { rowspan: 0, colspan: 0 }
+  return { rowspan: categoryRowSpanMap.value[row.category], colspan: 1 }
+}
+
+watch(
+  () => [props.visible, props.id] as const,
+  ([visible, id]) => {
+    if (!visible || !id) return
+    loadDetail(id)
+  },
+  { immediate: true }
+)
+</script>
+
+<template>
+  <el-drawer
+    :model-value="visible"
+    :size="'100%'"
+    :with-header="false"
+    destroy-on-close
+    body-class="qhse-report-preview-drawer__body"
+    @update:model-value="handleVisibleChange">
+    <div class="qhse-report-preview" v-loading="loading">
+      <div class="qhse-report-preview__sheet">
+        <div class="qhse-report-preview__sheet-title">QHSE 月度报告</div>
+        <div class="qhse-report-preview__meta">
+          <span>年月:{{ formatDisplayValue('yearMonths') }}</span>
+          <span>填报人:{{ formatDisplayValue('personName') }}</span>
+          <span>创建日期:{{ formatDisplayValue('createTime') }}</span>
+        </div>
+
+        <div class="qhse-report-preview__table-wrap">
+          <el-table
+            :data="tableRows"
+            :span-method="tableSpanMethod"
+            border
+            stripe
+            height="60vh"
+            class="qhse-report-preview__el-table">
+            <el-table-column
+              prop="category"
+              label="基本信息"
+              fixed="left"
+              width="140"
+              align="center" />
+            <el-table-column prop="label" label="单位" fixed="left" width="220" align="center" />
+            <el-table-column
+              v-for="company in companyColumns"
+              :key="company.key"
+              :prop="company.key"
+              :label="company.label"
+              min-width="140"
+              align="center" />
+            <el-table-column
+              prop="summary"
+              label="汇总"
+              min-width="180"
+              align="center"
+              fixed="right" />
+          </el-table>
+        </div>
+      </div>
+
+      <div class="qhse-report-preview__footer">
+        <el-button size="large" type="primary" @click="handleVisibleChange(false)">关闭</el-button>
+      </div>
+    </div>
+  </el-drawer>
+</template>
+
+<style scoped lang="scss">
+.qhse-report-preview {
+  display: flex;
+  min-height: 100%;
+  flex-direction: column;
+  gap: 20px;
+  background: linear-gradient(180deg, #eef4ff 0%, #f7f9fc 220px, #eef2f7 100%);
+  padding: 24px;
+}
+
+.qhse-report-preview__sheet {
+  display: flex;
+  min-height: 0;
+  flex: 1;
+  flex-direction: column;
+  gap: 16px;
+  background: #fff;
+  border: 1px solid #d8e0ef;
+  border-radius: 10px;
+  box-shadow: 0 18px 50px rgb(35 51 84 / 10%);
+  padding: 24px;
+}
+
+.qhse-report-preview__sheet-title {
+  text-align: center;
+  font-size: 28px;
+  font-weight: 700;
+  letter-spacing: 2px;
+  color: #203354;
+}
+
+.qhse-report-preview__meta {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px 24px;
+  color: #52627f;
+  font-size: 14px;
+}
+
+.qhse-report-preview__table-wrap {
+  min-height: 0;
+  flex: 1;
+}
+
+.qhse-report-preview__footer {
+  position: sticky;
+  bottom: 0;
+  z-index: 20;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-top: auto;
+  padding: 12px;
+  border-radius: 10px;
+  background: rgb(255 255 255 / 90%);
+  backdrop-filter: blur(8px);
+}
+
+@media (width < 768px) {
+  .qhse-report-preview {
+    padding: 12px;
+    gap: 12px;
+  }
+
+  .qhse-report-preview__sheet {
+    padding: 14px;
+    border-radius: 14px;
+  }
+
+  .qhse-report-preview__sheet-title {
+    font-size: 22px;
+    letter-spacing: 1px;
+  }
+}
+
+:deep(.qhse-report-preview-drawer__body) {
+  height: 100%;
+  overflow-y: auto;
+  padding: 0;
+}
+
+:deep(.qhse-report-preview__el-table) {
+  --el-table-header-bg-color: #dbe8ff;
+  --el-table-header-text-color: #183153;
+  --el-table-row-hover-bg-color: #f8fbff;
+}
+
+:deep(.qhse-report-preview__el-table .el-table__cell) {
+  padding: 0;
+}
+
+:deep(.qhse-report-preview__el-table th.el-table__cell) {
+  font-weight: 700;
+}
+
+:deep(.qhse-report-preview__el-table .cell) {
+  padding: 12px 14px;
+  line-height: 1.5;
+  word-break: break-word;
+}
+
+:deep(.qhse-report-preview__el-table .el-table__body-wrapper td:last-child) {
+  background: #eef4ff;
+  color: #14346b;
+  font-weight: 700;
+}
+
+:deep(.qhse-report-preview__el-table .el-table__body-wrapper td:nth-child(2)) {
+  background: #f8fbff;
+  font-weight: 600;
+}
+
+:deep(.qhse-report-preview__el-table .el-table__body-wrapper td:nth-child(n + 3):not(:last-child)) {
+  color: #0f3f8f;
+  font-weight: 600;
+}
+</style>

+ 57 - 0
src/views/pms/qhse/reportSummary/types.ts

@@ -0,0 +1,57 @@
+export interface QhseMonthReportItem {
+  id: number
+  title?: string
+  yearMonths?: string
+  deptId?: number
+  deptName?: string
+  personName?: string
+  createTime?: string
+  employee?: string | number
+  subcontractors?: string | number
+  drivingMileage?: string | number
+  totalManHours?: string | number
+  withoutAccident?: string | number
+  fatality?: string | number
+  injury?: string | number
+  restrictedCase?: string | number
+  medicalCase?: string | number
+  firstAidCase?: string | number
+  vehicleAccident?: string | number
+  nearMiss?: string | number
+  spill?: string | number
+  lifeSavingRules?: string | number
+  toolboxTalk?: string | number
+  committeeMeeting?: string | number
+  monthlyMeeting?: string | number
+  companyHazard?: string | number
+  qhseInspection?: string | number
+  socCards?: string | number
+  ptwAudit?: string | number
+  jsa?: string | number
+  drills?: string | number
+  training?: string | number
+  participantsTraining?: string | number
+  trainingsHours?: string | number
+  waterConsumption?: string | number
+  dieselConsumption?: string | number
+  electricityConsumption?: string | number
+  naturalGasConsumption?: string | number
+  dutyPerson?: number
+  remark?: string
+}
+
+export type QhseMonthReportListItem = QhseMonthReportItem
+
+export interface ReportCompanyColumn {
+  key: string
+  label: string
+}
+
+export type ReportMetricValue = string | number
+
+export interface ReportMetricRow {
+  category: string
+  label: string
+  field: keyof QhseMonthReportItem
+  unit: string
+}

+ 97 - 77
src/views/report-statistics/inspection_order/index.vue

@@ -14,17 +14,15 @@
                   queryParams.pageNo = 1
                   getList('all')
                 }
-              "
-            >
-              <Icon icon="ep:histogram" :size="40" />
-              <div class="card-title">工单数量</div>
+              ">
+              <Icon icon="ep:histogram" class="text-[#2563eb]!" :size="40" />
+              <div class="card-title text-[#6b7280]!">工单数量</div>
 
               <div class="card-value pt-5">
                 <CountTo
-                  class="text-2xl"
+                  class="text-3xl text-[#2563eb]!"
                   :end-val="deviceCount.finished + deviceCount.ignore + deviceCount.todo || 0"
-                  :decimals="0"
-                />
+                  :decimals="0" />
               </div>
             </div>
           </div>
@@ -39,13 +37,15 @@
                 getList('finished')
               }
             "
-            :class="{ 'stat-card-selected': statusList.finished }"
-          >
+            :class="{ 'stat-card-selected': statusList.finished }">
             <div class="stat-card bg-green-gradient">
-              <Icon icon="ep:finished" :size="40" />
+              <Icon icon="ep:finished" class="text-[#219241]!" :size="40" />
               <div class="card-title">完成数量</div>
               <div class="card-value pt-5">
-                <CountTo class="text-2xl" :end-val="deviceCount.finished || 0" :decimals="0" />
+                <CountTo
+                  class="text-3xl text-[#219241]!"
+                  :end-val="deviceCount.finished || 0"
+                  :decimals="0" />
               </div>
             </div>
           </div>
@@ -60,13 +60,15 @@
                 getList('todo')
               }
             "
-            :class="{ 'stat-card-selected': statusList.todo }"
-          >
+            :class="{ 'stat-card-selected': statusList.todo }">
             <div class="stat-card bg-orange-gradient">
-              <Icon icon="ep:more-filled" :size="40" />
+              <Icon icon="ep:more-filled" class="text-[#fc9a03]!" :size="40" />
               <div class="card-title">未完成数量</div>
               <div class="card-value pt-5">
-                <CountTo class="text-2xl" :end-val="deviceCount.todo || 0" :decimals="0" />
+                <CountTo
+                  class="text-3xl text-[#fc9a03]!"
+                  :end-val="deviceCount.todo || 0"
+                  :decimals="0" />
               </div>
             </div>
           </div>
@@ -81,13 +83,15 @@
                 getList('ignore')
               }
             "
-            :class="{ 'stat-card-selected': statusList.ignore }"
-          >
+            :class="{ 'stat-card-selected': statusList.ignore }">
             <div class="stat-card bg-green-gradient">
               <Icon icon="ep:hide" :size="40" />
               <div class="card-title">已忽略</div>
               <div class="card-value pt-5">
-                <CountTo class="text-2xl" :end-val="deviceCount.ignore || 0" :decimals="0" />
+                <CountTo
+                  class="text-3xl text-[#6b7280]!"
+                  :end-val="deviceCount.ignore || 0"
+                  :decimals="0" />
               </div>
             </div>
           </div>
@@ -101,16 +105,17 @@
                 queryParams.pageNo = 1
                 getExceptionList()
               }
-            "
-          >
+            ">
             <div
               class="stat-card bg-red-gradient"
-              :class="{ 'stat-card-selected': statusList.exception }"
-            >
-              <Icon icon="ep:bell" :size="40" />
+              :class="{ 'stat-card-selected': statusList.exception }">
+              <Icon icon="ep:bell" class="text-[#d45336]!" :size="40" />
               <div class="card-title">异常设备数量</div>
               <div class="card-value pt-5">
-                <CountTo class="text-2xl" :end-val="exceptions?.exceptionNum || 0" :decimals="0" />
+                <CountTo
+                  class="text-3xl text-[#d45336]!"
+                  :end-val="exceptions?.exceptionNum || 0"
+                  :decimals="0" />
               </div>
             </div>
           </div>
@@ -124,16 +129,17 @@
                 queryParams.pageNo = 1
                 getExceptionPoint()
               }
-            "
-          >
+            ">
             <div
               class="stat-card bg-warn-gradient"
-              :class="{ 'stat-card-selected': statusList.exceptionPoint }"
-            >
-              <Icon icon="ep:info-filled" :size="40" />
+              :class="{ 'stat-card-selected': statusList.exceptionPoint }">
+              <Icon icon="ep:info-filled" class="text-[#e6a23c]!" :size="40" />
               <div class="card-title">异常点数量</div>
               <div class="card-value pt-5">
-                <CountTo class="text-2xl" :end-val="exceptionPoint.value || 0" :decimals="0" />
+                <CountTo
+                  class="text-3xl text-[#e6a23c]!"
+                  :end-val="exceptionPoint.value || 0"
+                  :decimals="0" />
               </div>
             </div>
           </div>
@@ -163,8 +169,7 @@
           :loading="loading"
           :data="list3"
           :stripe="true"
-          :show-overflow-tooltip="true"
-        >
+          :show-overflow-tooltip="true">
           <zm-table-column :label="t('iotDevice.serial')" width="70" align="center">
             <template #default="scope">
               {{ scope.$index + 1 }}
@@ -188,8 +193,7 @@
           :loading="loading"
           :data="list2"
           :stripe="true"
-          :show-overflow-tooltip="true"
-        >
+          :show-overflow-tooltip="true">
           <zm-table-column :label="t('iotDevice.serial')" width="70" align="center">
             <template #default="scope">
               {{ scope.$index + 1 }}
@@ -211,8 +215,7 @@
             :label="t('inspect.exceptionDes')"
             align="center"
             prop="description"
-            action
-          />
+            action />
         </zm-table>
 
         <zm-table
@@ -222,8 +225,7 @@
           :data="list"
           :show-overflow-tooltip="true"
           @row-click="handleRowClick"
-          height="48vh"
-        >
+          height="48vh">
           <zm-table-column :label="t('iotDevice.serial')" width="70" align="center">
             <template #default="scope">
               {{ scope.$index + 1 }}
@@ -238,14 +240,12 @@
             :label="t('route.orderType')"
             align="center"
             prop="type"
-            min-width="90"
-          />
+            min-width="90" />
           <zm-table-column
             :label="t('operationFill.status')"
             align="center"
             prop="status"
-            min-width="95"
-          >
+            min-width="95">
             <template #default="scope">
               <dict-tag :type="DICT_TYPE.PMS_INSPECT_ORDER_STATUS" :value="scope.row.status" />
             </template>
@@ -258,16 +258,14 @@
           <zm-table-column
             :label="t('iotMaintain.PersonInCharge')"
             align="center"
-            prop="chargeName"
-          />
+            prop="chargeName" />
 
           <zm-table-column
             :label="t('iotMaintain.operation')"
             align="center"
             min-width="150px"
             fixed="right"
-            action
-          >
+            action>
             <template #default="scope">
               <el-button link type="primary" @click="openForm(scope.row.id)">
                 异常巡检点
@@ -281,8 +279,7 @@
           :total="total"
           v-model:page="queryParams.pageNo"
           v-model:limit="queryParams.pageSize"
-          @pagination="getAllList"
-        />
+          @pagination="getAllList" />
       </ContentWrap>
     </el-col>
   </el-row>
@@ -310,16 +307,14 @@
         :label="t('inspect.exceptionDes')"
         align="center"
         prop="description"
-        action
-      />
+        action />
     </zm-table>
 
     <Pagination
       :total="detailTotal"
       v-model:page="detailQueryParams.pageNo"
       v-model:limit="detailQueryParams.pageSize"
-      @pagination="goDetail(detailQueryParams.deviceCode)"
-    />
+      @pagination="goDetail(detailQueryParams.deviceCode)" />
   </el-drawer>
 </template>
 
@@ -694,35 +689,37 @@ onMounted(() => {
 }
 
 .stat-card {
-  padding: 20px;
+  padding: 14px 16px;
+  background: linear-gradient(180deg, #ffffff 0%, #f7faff 100%);
+  border: 1px solid #e4ecf7;
   border-radius: 10px;
-  color: white;
+  box-shadow: 0 4px 12px rgb(31 91 184 / 8%);
   text-align: center;
   font-size: 14px;
-  /* box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); */
   transition:
     transform 0.3s ease,
-    box-shadow 0.3s ease;
-  backdrop-filter: blur(12px);
+    box-shadow 0.3s ease,
+    border-color 0.3s ease;
+  color: #6b7280;
   height: 170px;
   cursor: pointer;
-  overflow: hidden; /* 防止闪光效果溢出 */
-}
-.stat-card::before {
-  position: absolute;
-  filter: blur(20px);
-  z-index: -1;
+  overflow: hidden;
 }
 
 .card-title {
   margin: 8px 0;
-  font-size: 16px;
+  font-size: 14px;
+  font-weight: 600;
+  color: #6b7280;
+  line-height: 1.4;
 }
 
 .card-value {
-  font-size: 28px;
-  font-weight: bold;
   margin: 8px 0;
+  font-size: 28px;
+  font-weight: 700;
+  line-height: 1;
+  color: #1f5bb8;
 }
 
 .card-trend {
@@ -730,28 +727,52 @@ onMounted(() => {
   opacity: 0.9;
 }
 
-/* 毛玻璃渐变背景 —— 不再使用 background-image */
 .bg-blue-gradient {
-  background: linear-gradient(135deg, rgba(77, 147, 255, 0.5), rgba(75, 132, 254));
+  background: linear-gradient(180deg, #ffffff 0%, #f7faff 100%);
+  border-color: #e4ecf7;
+}
+
+.bg-blue-gradient .card-value {
+  color: #2563eb;
 }
 
 .bg-green-gradient {
-  background: linear-gradient(135deg, rgba(101, 226, 136, 0.3), #52d7a2);
+  background: linear-gradient(180deg, #f7fcf8 0%, #edf8f1 100%);
+  border-color: #d6efdc;
+}
+
+.bg-green-gradient .card-value {
+  color: #16a34a;
 }
 
 .bg-orange-gradient {
-  background: linear-gradient(135deg, rgba(152, 82, 4, 0.5), rgba(255, 152, 4, 0.6));
+  background: linear-gradient(180deg, #fff8ef 0%, #ffeed9 100%);
+  border-color: #ffd7a1;
+}
+
+.bg-orange-gradient .card-value {
+  color: #d97706;
 }
 
 .bg-red-gradient {
-  background: linear-gradient(135deg, rgba(232, 65, 51), rgba(252, 242, 236));
+  background: linear-gradient(180deg, #fff4f4 0%, #ffe8e8 100%);
+  border-color: #ffcfcf;
+}
+
+.bg-red-gradient .card-value {
+  color: #de3b3b;
 }
 
 .bg-warn-gradient {
-  background: linear-gradient(135deg, rgba(255, 201, 103), rgba(243, 162, 152));
+  background: linear-gradient(180deg, #fff8ef 0%, #ffeed9 100%);
+  border-color: #ffd7a1;
+}
+
+.bg-warn-gradient .card-value {
+  color: #d97706;
 }
 
-/* 确保内容不溢出 */
+/* ???????????*/
 :deep(.el-row) {
   flex-wrap: wrap;
 }
@@ -771,10 +792,9 @@ onMounted(() => {
 
 .stat-card-selected {
   position: relative;
-  transform: scale(1.06);
+  transform: translateY(-2px);
   transition: all 0.2s;
-  box-shadow:
-    0 10px 20px rgba(0, 80, 179, 0.5),
-    0 0 10px rgba(0, 120, 255, 0.4) inset;
+  box-shadow: 0 10px 20px rgb(31 91 184 / 12%);
+  border-color: #bfd5ee;
 }
 </style>