فهرست منبع

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

yanghao 3 روز پیش
والد
کامیت
6705acfd37

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

@@ -501,3 +501,36 @@ export const CertPersonApi = {
     return await request.get({ url: `/rq/qhse-cert-person/get?id=` + id })
   }
 }
+
+// QHSE月报 API
+export const QhseMonthReportApi = {
+  // 查询QHSE月报分页
+  getQhseMonthReportPage: async (params: any) => {
+    return await request.get({ url: `/rq/qhse-month-report/page`, params })
+  },
+
+  // 查询QHSE月报详情
+  getQhseMonthReport: async (id) => {
+    return await request.get({ url: `/rq/qhse-month-report/get?id=` + id })
+  },
+
+  // 新增QHSE月报
+  createQhseMonthReport: async (data) => {
+    return await request.post({ url: `/rq/qhse-month-report/create`, data })
+  },
+
+  // 修改QHSE月报
+  updateQhseMonthReport: async (data) => {
+    return await request.put({ url: `/rq/qhse-month-report/update`, data })
+  },
+
+  // 删除QHSE月报
+  deleteQhseMonthReport: async (id) => {
+    return await request.delete({ url: `/rq/qhse-month-report/delete?id=` + id })
+  },
+
+  // 导出QHSE月报 Excel
+  exportQhseMonthReport: async (params) => {
+    return await request.download({ url: `/rq/qhse-month-report/export-excel`, params })
+  }
+}

+ 120 - 0
src/components/DeptSelectForm/qhseDept.vue

@@ -0,0 +1,120 @@
+<template>
+  <Dialog v-model="dialogVisible" title="部门选择" width="600">
+    <el-row v-loading="formLoading">
+      <el-col :span="24">
+        <ContentWrap class="h-1/1">
+          <el-tree-select
+            ref="treeRef"
+            :data="deptTree"
+            :props="defaultProps"
+            show-checkbox
+            :check-strictly="true"
+            :multiple="false"
+            check-on-click-node
+            highlight-current
+            node-key="id"
+            @check="handleCheck" />
+        </ContentWrap>
+      </el-col>
+    </el-row>
+    <template #footer>
+      <el-button
+        :disabled="formLoading || !selectedDeptIds?.length"
+        type="primary"
+        @click="submitForm">
+        确 定
+      </el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+
+defineOptions({ name: 'DeptSelectForm' })
+
+const emit = defineEmits<{
+  confirm: [deptList: any[]]
+}>()
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const props = defineProps({
+  // 是否严格的遵循父子不互相关联
+  checkStrictly: {
+    type: Boolean,
+    default: false
+  },
+  // 是否支持多选
+  multiple: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const treeRef = ref()
+const deptTree = ref<Tree[]>([]) // 部门树形结构
+const selectedDeptIds = ref<number[]>([]) // 选中的部门 ID 列表
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+
+/** 打开弹窗 */
+const open = async (selectedList?: DeptApi.DeptVO[]) => {
+  resetForm()
+  formLoading.value = true
+  try {
+    // 加载部门列表
+    const deptData = await DeptApi.getSimpleDeptList()
+    deptTree.value = handleTree(deptData)
+  } finally {
+    formLoading.value = false
+  }
+  dialogVisible.value = true
+  // 设置已选择的部门
+  if (selectedList?.length) {
+    await nextTick()
+    const selectedIds = selectedList
+      .map((dept) => dept.id)
+      .filter((id): id is number => id !== undefined)
+    selectedDeptIds.value = selectedIds
+    treeRef.value?.setCheckedKeys(selectedIds)
+  }
+}
+
+/** 处理选中状态变化 */
+const handleCheck = (data: any, checked: any) => {
+  selectedDeptIds.value = treeRef.value.getCheckedKeys()
+  if (!props.multiple && selectedDeptIds.value.length > 1) {
+    // 单选模式下,只保留最后选择的节点
+    const lastSelectedId = selectedDeptIds.value[selectedDeptIds.value.length - 1]
+    selectedDeptIds.value = [lastSelectedId]
+    treeRef.value.setCheckedKeys([lastSelectedId])
+  }
+}
+
+/** 提交选择 */
+const submitForm = async () => {
+  try {
+    // 获取选中的完整部门数据
+    const checkedNodes = treeRef.value.getCheckedNodes()
+    message.success(t('common.updateSuccess'))
+    dialogVisible.value = false
+    emit('confirm', checkedNodes)
+  } finally {
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  deptTree.value = []
+  selectedDeptIds.value = []
+  if (treeRef.value) {
+    treeRef.value.setCheckedKeys([])
+  }
+}
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+</script>

+ 31 - 1
src/layout/components/Menu/src/components/useRenderMenuItem.tsx

@@ -98,29 +98,59 @@ export const useRenderMenuItem = () =>
                 '计量器具管理',
                 '计量器具台账',
                 '检测证书管控',
+                '计量器具报废',
+                '设备检测管控',
+                '应检设备台账',
+                '检测证书管理',
+                '质量统筹管控',
+                '施工质量管控',
+                '产品服务质量',
+                '客户满意度',
                 '安全风控',
                 '行为安全管理',
                 '分析数据源',
                 '安全行为观察',
                 '工艺安全管理',
+                '变更管理',
                 '危险源辨识',
                 '工作安全分析',
                 '高危作业许可',
                 '系统安全管理',
+                '体系建设认证',
+                '内审',
+                '管理评审',
+                '外审',
                 '特种作业人员',
                 '人员台账',
                 '人员证书',
+                '安全培训',
+                '安全培训计划',
+                '安全培训档案',
+
                 '隐患排查',
                 '隐患排查分类',
                 '隐患排查记录',
                 '应急体系',
+                '预案台账',
+                '应急物资',
+                '海外应急处置',
+                '应急演练',
                 '突发事件应急处理',
+                '安全绩效',
+                '安全考核',
                 '环境合规',
+                '危废处置',
+                '环境事件处置',
                 '环境因素识别',
                 '职业健康',
                 '劳保用品',
                 '职业健康体检',
-                '健康培训'
+                '健康培训',
+                '突发事件处置',
+                'QHSE资料库',
+                'QHSE资质证书',
+                'QHSE体系文件',
+                '事故案例'
               ].includes(v.meta?.title)
             )
           } else {

+ 19 - 8
src/utils/routerHelper.ts

@@ -5,6 +5,23 @@ import { cloneDeep, omit } from 'lodash-es'
 import qs from 'qs'
 
 const modules = import.meta.glob('../views/**/*.{vue,tsx}')
+const getCurrentSource = () => sessionStorage.getItem('LOGIN_SOURCE') || ''
+
+const getRouteComponent = (route: AppCustomRouteRecordRaw, modulesRoutesKeys: string[]) => {
+  const index = route?.component
+    ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component))
+    : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path))
+
+  if (index >= 0) {
+    return modules[modulesRoutesKeys[index]]
+  }
+
+  if (getCurrentSource() === 'qhse_nav') {
+    return () => import('@/views/Error/ComingSoon.vue')
+  }
+
+  return undefined
+}
 /**
  * 注册一个异步组件
  * @param componentPath 例:/bpm/oa/leave/detail
@@ -115,10 +132,7 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
         redirect: route.redirect,
         meta: meta
       }
-      const index = route?.component
-        ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component))
-        : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path))
-      childrenData.component = modules[modulesRoutesKeys[index]]
+      childrenData.component = getRouteComponent(route, modulesRoutesKeys)
       data.children = [childrenData]
     } else {
       // 目录
@@ -138,10 +152,7 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
         // 菜单
       } else {
         // 对后端传component组件路径和不传做兼容(如果后端传component组件路径,那么path可以随便写,如果不传,component组件路径会根path保持一致)
-        const index = route?.component
-          ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component))
-          : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path))
-        data.component = modules[modulesRoutesKeys[index]]
+        data.component = getRouteComponent(route, modulesRoutesKeys)
       }
       if (route.children) {
         data.children = generateRoute(route.children)

+ 59 - 0
src/views/Error/ComingSoon.vue

@@ -0,0 +1,59 @@
+<template>
+  <div class="coming-soon">
+    <div class="coming-soon__card">
+      <div class="coming-soon__badge">QHSE</div>
+      <h1>功能正在开发中,敬请期待</h1>
+      <p>该功能暂未开放,后续版本将尽快补齐。</p>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+defineOptions({ name: 'ComingSoon' })
+</script>
+
+<style scoped>
+.coming-soon {
+  min-height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 32px;
+}
+
+.coming-soon__card {
+  width: min(560px, 100%);
+  padding: 48px 40px;
+  border-radius: 20px;
+  background: var(--el-bg-color);
+  box-shadow: 0 12px 32px rgb(0 0 0 / 8%);
+  text-align: center;
+}
+
+.coming-soon__badge {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 16px;
+  padding: 6px 12px;
+  border-radius: 999px;
+  background: var(--el-color-primary-light-9);
+  color: var(--el-color-primary);
+  font-size: 12px;
+  font-weight: 600;
+  letter-spacing: 0.08em;
+}
+
+h1 {
+  margin: 0;
+  font-size: 28px;
+  line-height: 1.25;
+  color: var(--el-text-color-primary);
+}
+
+p {
+  margin: 12px 0 0;
+  color: var(--el-text-color-secondary);
+  font-size: 14px;
+}
+</style>

+ 21 - 4
src/views/pms/qhse/iotmeasuredetect/index.vue

@@ -6,6 +6,13 @@
       <ContentWrap style="border: none">
         <!-- 搜索工作栏 -->
         <el-form :model="queryParams" ref="queryFormRef" :inline="true">
+          <el-form-item label="计量器具编码" prop="measureCode">
+            <el-input
+              v-model="queryParams.measureCode"
+              placeholder="请输入计量器具编码"
+              clearable
+              class="!w-150px" />
+          </el-form-item>
           <el-form-item label="检测/校准日期" prop="detectDate">
             <el-date-picker
               v-model="queryParams.detectDate"
@@ -25,13 +32,22 @@
               class="!w-150px" />
           </el-form-item>
           <el-form-item label="检测/校准有效期" prop="validityPeriod">
-            <el-date-picker
+            <!-- <el-date-picker
               v-model="queryParams.validityPeriod"
               value-format="YYYY-MM-DD"
-              type="date"
+              type="daterange"
               placeholder="选择检测/校准有效期"
               clearable
-              class="!w-150px" />
+              class="!w-150px" /> -->
+
+            <el-date-picker
+              v-model="queryParams.validityPeriod"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="daterange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+              class="!w-200px" />
           </el-form-item>
 
           <el-form-item>
@@ -173,7 +189,8 @@ const queryParams = reactive({
   validityPeriod: undefined,
   detectAmount: undefined,
   createTime: [],
-  deptId: undefined
+  deptId: undefined,
+  measureCode: undefined
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中

+ 769 - 1
src/views/pms/qhse/monthlyReport/MonthlyReportAdd.vue

@@ -1,7 +1,775 @@
 <template>
-  <div>addddd</div>
+  <div class="monthly-report-add">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="auto"
+      class="report-form">
+      <!-- 1. 基本信息 -->
+      <el-card class="form-section" shadow="hover">
+        <template #header>
+          <div class="section-header">
+            <Icon icon="ep:document" class="mr-5px" />
+            <span>基本信息</span>
+          </div>
+        </template>
+        <el-row :gutter="16">
+          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
+            <el-form-item label="部门名称" prop="deptId">
+              <el-input
+                v-model="deptName"
+                placeholder="请选择部门"
+                readonly
+                @click="openDeptSelect">
+                <template #suffix>
+                  <Icon icon="ep:search" class="cursor-pointer" />
+                </template>
+              </el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
+            <el-form-item label="月报标题" prop="title">
+              <el-input v-model="formData.title" placeholder="请输入月报标题" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
+            <el-form-item label="年月" prop="yearMonth">
+              <el-date-picker
+                v-model="formData.yearMonth"
+                type="month"
+                placeholder="选择年月"
+                format="YYYY-MM"
+                value-format="YYYY-MM"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-card>
+
+      <!-- 2. 人工时和安全行驶公里数 -->
+      <el-card class="form-section" shadow="hover">
+        <template #header>
+          <div class="section-header">
+            <Icon icon="ep:user" class="mr-5px" />
+            <span>人工时和安全行驶公里数</span>
+          </div>
+        </template>
+        <el-row :gutter="16">
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="员工人数" prop="employee">
+              <el-input-number
+                v-model="formData.employee"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="分包商人数" prop="subcontractors">
+              <el-input-number
+                v-model="formData.subcontractors"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="安全行驶里程数(公里)" prop="drivingMileage">
+              <el-input-number
+                v-model="formData.drivingMileage"
+                :min="0"
+                :precision="2"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="总人工时数(小时)" prop="totalManHours">
+              <el-input-number
+                v-model="formData.totalManHours"
+                :min="0"
+                :precision="2"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-card>
+
+      <!-- 3. QHSE被动性指标统计 -->
+      <el-card class="form-section" shadow="hover">
+        <template #header>
+          <div class="section-header">
+            <Icon icon="ep:warning" class="mr-5px" />
+            <span>QHSE被动性指标统计</span>
+          </div>
+        </template>
+        <el-row :gutter="16">
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="无事故累计天数" prop="withoutAccident">
+              <el-input-number
+                v-model="formData.withoutAccident"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="死亡事故(起)" prop="fatality">
+              <el-input-number
+                v-model="formData.fatality"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="损失工时事故(起)" prop="injury">
+              <el-input-number
+                v-model="formData.injury"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="工作受限事件(起)" prop="restrictedCase">
+              <el-input-number
+                v-model="formData.restrictedCase"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="医疗处理事件(起)" prop="medicalCase">
+              <el-input-number
+                v-model="formData.medicalCase"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="急救箱事件(起)" prop="firstAidCase">
+              <el-input-number
+                v-model="formData.firstAidCase"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="交通事故(起)" prop="vehicleAccident">
+              <el-input-number
+                v-model="formData.vehicleAccident"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="未遂事件(起)" prop="nearMiss">
+              <el-input-number
+                v-model="formData.nearMiss"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="泄漏事件(起)" prop="spill">
+              <el-input-number
+                v-model="formData.spill"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="违反保命规则的次数(次)" prop="lifeSavingRules">
+              <el-input-number
+                v-model="formData.lifeSavingRules"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-card>
+
+      <!-- 4. QHSE主动性指标统计 -->
+      <el-card class="form-section" shadow="hover">
+        <template #header>
+          <div class="section-header">
+            <Icon icon="ep:checked" class="mr-5px" />
+            <span>QHSE主动性指标统计</span>
+          </div>
+        </template>
+        <el-row :gutter="16">
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="班前会(次)" prop="toolboxTalk">
+              <el-input-number
+                v-model="formData.toolboxTalk"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="QHSE管理委员会会议(次)" prop="committeeMeeting">
+              <el-input-number
+                v-model="formData.committeeMeeting"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="QHSE月度例会(次)" prop="monthlyMeeting">
+              <el-input-number
+                v-model="formData.monthlyMeeting"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="公司级隐患排查(次)" prop="companyHazard">
+              <el-input-number
+                v-model="formData.companyHazard"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="QHSE检查(次)" prop="qhseInspection">
+              <el-input-number
+                v-model="formData.qhseInspection"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="安全观察卡(张)" prop="socCards">
+              <el-input-number
+                v-model="formData.socCards"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="工作许可审核(份)" prop="ptwAudit">
+              <el-input-number
+                v-model="formData.ptwAudit"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="工作安全分析(次)" prop="jsa">
+              <el-input-number
+                v-model="formData.jsa"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="演练次数" prop="drills">
+              <el-input-number
+                v-model="formData.drills"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="QHSE培训次数" prop="training">
+              <el-input-number
+                v-model="formData.training"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="QHSE培训人次" prop="participantsTraining">
+              <el-input-number
+                v-model="formData.participantsTraining"
+                :min="0"
+                :precision="0"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="QHSE培训学时数(小时)" prop="trainingsHours">
+              <el-input-number
+                v-model="formData.trainingsHours"
+                :min="0"
+                :precision="2"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-card>
+
+      <!-- 5. 环境数据 -->
+      <el-card class="form-section" shadow="hover">
+        <template #header>
+          <div class="section-header">
+            <Icon icon="ep:sunrise" class="mr-5px" />
+            <span>环境数据</span>
+          </div>
+        </template>
+        <el-row :gutter="16">
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="水消耗(吨)" prop="waterConsumption">
+              <el-input-number
+                v-model="formData.waterConsumption"
+                :min="0"
+                :precision="2"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="柴油消耗(升)" prop="dieselConsumption">
+              <el-input-number
+                v-model="formData.dieselConsumption"
+                :min="0"
+                :precision="2"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="用电量(千瓦·小时)" prop="electricityConsumption">
+              <el-input-number
+                v-model="formData.electricityConsumption"
+                :min="0"
+                :precision="2"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
+            <el-form-item label="天然气消耗量(立方米)" prop="naturalGasConsumption">
+              <el-input-number
+                v-model="formData.naturalGasConsumption"
+                :min="0"
+                :precision="2"
+                controls-position="right"
+                style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-card>
+
+      <!-- 6. 其他信息 -->
+      <el-card class="form-section" shadow="hover">
+        <template #header>
+          <div class="section-header">
+            <Icon icon="ep:info-filled" class="mr-5px" />
+            <span>其他信息</span>
+          </div>
+        </template>
+        <el-row :gutter="16">
+          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
+            <el-form-item label="工单填报人" prop="dutyPerson">
+              <el-input
+                v-model="dutyPersonName"
+                placeholder="请选择填报人"
+                readonly
+                @click="openUserSelect">
+                <template #suffix>
+                  <Icon icon="ep:search" class="cursor-pointer" />
+                </template>
+              </el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="16">
+          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+            <el-form-item label="备注" prop="remark">
+              <el-input
+                v-model="formData.remark"
+                type="textarea"
+                :rows="4"
+                placeholder="请输入备注信息" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-card>
+
+      <!-- 操作按钮 -->
+      <div class="form-actions">
+        <el-button @click="handleCancel">取消</el-button>
+        <el-button type="primary" @click="handleSubmit" :loading="submitLoading"> 提交 </el-button>
+      </div>
+    </el-form>
+
+    <!-- 部门选择弹窗 -->
+    <DeptSelectForm ref="deptSelectFormRef" :multiple="false" @confirm="handleDeptConfirm" />
+
+    <!-- 用户选择弹窗 -->
+    <UserSelectForm ref="userSelectFormRef" @confirm="handleUserConfirm" />
+  </div>
 </template>
 
 <script setup lang="ts">
+import { reactive, ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { FormInstance, FormRules } from 'element-plus'
+import { QhseMonthReportApi } from '@/api/pms/qhse'
+// import DeptSelectForm from '@/components/DeptSelectForm/qhseDept.vue'
+import UserSelectForm from '@/components/UserSelectForm/index.vue'
+import { handleTree, defaultProps } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+const deptList2 = ref<Tree[]>([]) // 树形结构
+
 defineOptions({ name: 'MonthlyReportAdd' })
+
+const router = useRouter()
+const message = useMessage()
+
+// 表单引用
+const formRef = ref<FormInstance>()
+const submitLoading = ref(false)
+
+// 表单数据
+const formData = reactive({
+  title: '',
+  yearMonth: '',
+  deptId: 0,
+  employee: '0',
+  subcontractors: '0',
+  drivingMileage: '0',
+  totalManHours: '0',
+  withoutAccident: '0',
+  fatality: '0',
+  injury: '0',
+  restrictedCase: '0',
+  medicalCase: '0',
+  firstAidCase: '0',
+  vehicleAccident: '0',
+  nearMiss: '0',
+  spill: '0',
+  lifeSavingRules: '0',
+  toolboxTalk: '0',
+  committeeMeeting: '0',
+  monthlyMeeting: '0',
+  companyHazard: '0',
+  qhseInspection: '0',
+  socCards: '0',
+  ptwAudit: '0',
+  jsa: '0',
+  drills: '0',
+  training: '0',
+  participantsTraining: '0',
+  trainingsHours: '0',
+  waterConsumption: '0',
+  dieselConsumption: '0',
+  electricityConsumption: '0',
+  naturalGasConsumption: '0',
+  dutyPerson: 0,
+  remark: ''
+})
+
+// 表单校验规则
+const formRules = reactive<FormRules>({
+  title: [{ required: true, message: '月报标题不能为空', trigger: 'blur' }],
+  yearMonth: [{ required: true, message: '年月不能为空', trigger: 'change' }],
+  deptId: [{ required: true, message: '部门不能为空', trigger: 'change' }],
+  dutyPerson: [{ required: true, message: '工单填报人不能为空', trigger: 'change' }]
+})
+
+// 部门名称显示
+const deptName = ref('')
+
+// 填报人名称显示
+const dutyPersonName = ref('')
+
+// 部门选择弹窗引用
+const deptSelectFormRef = ref()
+
+// 用户选择弹窗引用
+const userSelectFormRef = ref()
+
+/** 打开部门选择 */
+const openDeptSelect = () => {
+  deptSelectFormRef.value?.open()
+}
+
+/** 部门选择确认 */
+const handleDeptConfirm = async (deptList: any[]) => {
+  if (deptList && deptList.length > 0) {
+    const dept = deptList[0]
+    formData.deptId = dept.id
+    deptName.value = dept.name
+  }
+}
+
+/** 打开用户选择 */
+const openUserSelect = () => {
+  userSelectFormRef.value?.open()
+}
+
+/** 用户选择确认 */
+const handleUserConfirm = (userId: any, userList: any[]) => {
+  if (userList && userList.length > 0) {
+    const user = userList[0]
+    formData.dutyPerson = user.id
+    dutyPersonName.value = user.nickname
+  }
+}
+
+/** 提交表单 */
+const handleSubmit = async () => {
+  if (!formRef.value) return
+
+  // 校验表单
+  const valid = await formRef.value.validate()
+  if (!valid) return
+
+  submitLoading.value = true
+  try {
+    await QhseMonthReportApi.createQhseMonthReport(formData)
+    message.success('新增成功')
+    // 返回列表页或上一页
+    router.back()
+  } catch (error) {
+    console.error('提交失败:', error)
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+/** 取消 */
+const handleCancel = () => {
+  router.back()
+}
+
+onMounted(async () => {
+  deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+})
 </script>
+<style scoped lang="scss">
+.monthly-report-add {
+  padding: 12px;
+  min-height: 100vh;
+  background-color: #f5f7fa;
+
+  .report-form {
+    max-width: 1400px;
+    margin: 0 auto;
+
+    .form-section {
+      margin-bottom: 16px;
+      border-radius: 8px;
+
+      :deep(.el-card__header) {
+        padding: 12px 16px;
+        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+        color: white;
+        border-radius: 8px 8px 0 0;
+      }
+
+      :deep(.el-card__body) {
+        padding: 16px;
+      }
+
+      .section-header {
+        display: flex;
+        align-items: center;
+        font-size: 15px;
+        font-weight: 600;
+      }
+
+      :deep(.el-form-item) {
+        margin-bottom: 16px;
+      }
+
+      :deep(.el-form-item__label) {
+        font-size: 14px;
+        line-height: 1.5;
+      }
+
+      :deep(.el-input),
+      :deep(.el-input-number),
+      :deep(.el-date-editor) {
+        width: 100%;
+      }
+
+      :deep(.el-input-number) {
+        .el-input__inner {
+          text-align: left;
+        }
+      }
+    }
+
+    .form-actions {
+      display: flex;
+      justify-content: center;
+      gap: 12px;
+      padding: 16px 0;
+      margin-top: 16px;
+      background: white;
+      border-radius: 8px;
+      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+      position: sticky;
+      bottom: 0;
+      z-index: 10;
+
+      .el-button {
+        min-width: 100px;
+        flex: 1;
+        max-width: 150px;
+        height: 44px;
+        font-size: 15px;
+      }
+    }
+  }
+}
+
+// 移动端优化
+@media screen and (max-width: 768px) {
+  .monthly-report-add {
+    padding: 8px;
+
+    .report-form {
+      .form-section {
+        margin-bottom: 12px;
+
+        :deep(.el-card__header) {
+          padding: 10px 12px;
+        }
+
+        :deep(.el-card__body) {
+          padding: 12px;
+        }
+
+        .section-header {
+          font-size: 14px;
+        }
+
+        :deep(.el-form-item__label) {
+          font-size: 13px;
+          padding-right: 8px;
+        }
+
+        :deep(.el-input__inner),
+        :deep(.el-textarea__inner) {
+          font-size: 14px;
+          min-height: 40px;
+        }
+
+        :deep(.el-input-number) {
+          .el-input-number__decrease,
+          .el-input-number__increase {
+            width: 32px;
+            height: 40px;
+            line-height: 40px;
+          }
+
+          .el-input__inner {
+            height: 40px;
+            line-height: 40px;
+          }
+        }
+
+        :deep(.el-date-editor) {
+          .el-input__inner {
+            height: 40px;
+            line-height: 40px;
+          }
+        }
+      }
+
+      .form-actions {
+        padding: 12px;
+        gap: 10px;
+        position: fixed;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        margin: 0;
+        border-radius: 0;
+        box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
+
+        .el-button {
+          height: 44px;
+          font-size: 15px;
+        }
+      }
+    }
+  }
+}
+
+// 超小屏幕优化
+@media screen and (max-width: 480px) {
+  .monthly-report-add {
+    padding: 6px;
+
+    .report-form {
+      .form-section {
+        :deep(.el-card__body) {
+          padding: 10px;
+        }
+
+        :deep(.el-form-item) {
+          margin-bottom: 12px;
+        }
+
+        :deep(.el-form-item__label) {
+          font-size: 12px;
+        }
+
+        :deep(.el-input__inner),
+        :deep(.el-textarea__inner) {
+          font-size: 13px;
+        }
+      }
+    }
+  }
+}
+</style>