ソースを参照

fix: 联调施工作业安全管理

sunqijun 3 ヶ月 前
コミット
02a8930b19

+ 28 - 1
src/api/production-safety/responsibility-implementation/index.ts

@@ -744,7 +744,7 @@ export function constructionSafetyQueryPageConstruction(params) {
  */
 export function constructionSafetyDeleteConstructionById(id) {
   return http.request({
-    url: `/constructionSafety/deleteConstructionById/${id}`,
+    url: `/constructionSafety/deleteConstructionById?id=${id}`,
     method: 'delete',
   });
 }
@@ -776,3 +776,30 @@ export function safetyResponsibilityDeptSaveSign(params) {
     params
   });
 }
+
+
+/**
+ * 管理端更新施工安全申请
+ * @param params - 更新后的施工安全申请数据(需包含 ID)
+ * @returns Promise<void>
+ */
+export function constructionSafetyUpdateApply(params) {
+  return http.request({
+    url: `/constructionSafety/updateApply`,
+    method: 'put',
+    params
+  });
+}
+
+/**
+ * 管理端保存施工安全数据
+ * @param params - 施工安全数据
+ * @returns Promise<void>
+ */ 
+export function constructionSafetySaveConstruction(params) {
+  return http.request({
+    url: `/constructionSafety/saveConstruction`,
+    method: 'post',
+    params
+  });
+}

+ 1 - 1
src/components/UploadFiles/UploadFiles.vue

@@ -13,7 +13,7 @@
       :id="inputId"
       class="upload-input"
       multiple
-      accept=".pdf,.docx,.xlsx,.pptx"
+      accept=".pdf,.docx,.doc,.xlsx,.pptx"
       @change="handleFileSelect"
       :disabled="isUploadDisabled"
     />

+ 4 - 4
src/views/production-safety/implement-safety-duty/create-responsibility-agree.vue

@@ -16,8 +16,8 @@
             style="width: 50%"
           />
         </el-form-item>
-        <el-form-item label="所属部门" prop="departmentName">
-          <el-select v-model="formValue.departmentName" size="large" placeholder="所属部门" style="width: 50%">
+        <el-form-item label="类别名称" prop="departmentName">
+          <el-select v-model="formValue.departmentName" size="large" placeholder="类别名称" style="width: 50%">
             <el-option value="院领导">院领导</el-option>
             <el-option value="所/中心/职能部门/直属研究部/分公司">所/中心/职能部门/直属研究部/分公司</el-option>
             <el-option value="所/中心级部门">所/中心级部门</el-option>
@@ -247,8 +247,8 @@
         const attachment = await formatAttachmentList(formValue.attachment);
         safetyResponsibilitySaveSafetyResponsibility({
           ...formValue,
-          planStartTime: formValue.planStartTime ? dayjs(formValue.planStartTime).format('YYYY-MM-DD') : null,
-          planEndTime: formValue.planEndTime ? dayjs(formValue.planEndTime).format('YYYY-MM-DD') : null,
+          // planStartTime: formValue.planStartTime ? dayjs(formValue.planStartTime).format('YYYY-MM-DD') : null,
+          // planEndTime: formValue.planEndTime ? dayjs(formValue.planEndTime).format('YYYY-MM-DD') : null,
           attachment: JSON.stringify(attachment),
           executeObject: Number(formValue.executeObject),
           userGroupId: formValue.userGroupId.join(','),

+ 4 - 4
src/views/production-safety/implement-safety-duty/edit-responsibility-agree.vue

@@ -13,8 +13,8 @@
             style="width: 50%"
           />
         </el-form-item>
-        <el-form-item label="所属部门" prop="departmentName">
-          <el-select v-model="formValue.departmentName" size="large" placeholder="所属部门" style="width: 50%">
+        <el-form-item label="类别名称" prop="departmentName">
+          <el-select v-model="formValue.departmentName" size="large" placeholder="类别名称" style="width: 50%">
             <el-option value="院领导">院领导</el-option>
             <el-option value="所/中心/职能部门/直属研究部/分公司">所/中心/职能部门/直属研究部/分公司</el-option>
             <el-option value="所/中心级部门">所/中心级部门</el-option>
@@ -249,8 +249,8 @@
         safetyResponsibilityUpdateSafetyResponsibility({
           ...formValue,
           id: Number(route.query.id),
-          planStartTime: formValue.planStartTime ? dayjs(formValue.planStartTime).format('YYYY-MM-DD') : null,
-          planEndTime: formValue.planEndTime ? dayjs(formValue.planEndTime).format('YYYY-MM-DD') : null,
+          // planStartTime: formValue.planStartTime ? dayjs(formValue.planStartTime).format('YYYY-MM-DD') : null,
+          // planEndTime: formValue.planEndTime ? dayjs(formValue.planEndTime).format('YYYY-MM-DD') : null,
           attachment: JSON.stringify(attachment),
           executeObject: Number(formValue.executeObject),
           userGroupId: formValue.userGroupId.join(','),

+ 4 - 6
src/views/production-safety/implement-safety-duty/responsibility-agree-manage-dept.vue

@@ -31,11 +31,11 @@
               <el-option :value="6" label="已作废" />
             </el-select>
           </el-form-item>
-          <el-form-item v-if="activeTab" label="所属部门">
+          <el-form-item v-if="activeTab" label="类别名称">
             <el-select
               v-model="queryParams.queryParam.departmentName"
               clearable
-              placeholder="所属部门"
+              placeholder="类别名称"
               style="width: 170px"
             >
               <el-option value="院领导">院领导</el-option>
@@ -100,10 +100,7 @@
             <template #default="scope">
               <div style="display: flex">
                 <el-button
-                  v-if="
-                    scope.row.status === 2 &&
-                    (scope.row.departmentName !== '员工' || scope.row.departmentName !== '常驻供应商')
-                  "
+                  v-if="scope.row.status === 2 && !/员工|常驻供应商/.test(scope.row.departmentName)"
                   type="primary"
                   link
                   @click="
@@ -336,6 +333,7 @@
         router.push({
           name: 'responsibilityAgreeManageDept',
         });
+        queryTableList();
       })
       .finally(() => {});
   };

+ 241 - 373
src/views/production-safety/implement-safety-duty/responsibility-agree-manage.vue

@@ -1,411 +1,279 @@
 <template>
   <div class="safety-platform-container">
     <header class="safety-platform-container__header">
-      <div class="breadcrumb-title"> 安全责任书管理 </div>
-      <el-tabs v-model="activeTab">
-        <el-tab-pane label="全部" name="" key="all" />
-        <el-tab-pane label="院领导" name="院领导" key="a" />
-        <el-tab-pane label="所/中心/职能部门/直属研究部/分公司" name="所/中心/职能部门/直属研究部/分公司" key="b" />
-        <el-tab-pane label="所/中心级部门" name="所/中心级部门" key="c" />
-        <el-tab-pane label="科室" name="科室" key="d" />
-        <el-tab-pane label="员工" name="员工" key="e" />
-        <el-tab-pane label="常驻供应商" name="常驻供应商" key="f" />
-      </el-tabs>
+      <div class="breadcrumb-title">
+        <BreadcrumbBack />
+        新增施工作业安全
+      </div>
     </header>
     <main class="safety-platform-container__main">
-      <div class="search-form">
-        <el-form :inline="true">
-          <el-form-item label="安全责任书">
-            <el-input
-              v-model="queryParams.queryParam.responsibilityName"
-              placeholder="搜索安全责任书名称"
-              style="width: 170px"
-            />
-          </el-form-item>
-          <el-form-item label="状态">
-            <el-select v-model="queryParams.queryParam.status" clearable placeholder="状态" style="width: 170px">
-              <el-option :value="1" label="未下发" />
-              <el-option :value="2" label="待签署" />
-              <el-option :value="3" label="待反馈材料" />
-              <el-option :value="4" label="待审核" />
-              <el-option :value="5" label="已完成" />
-              <el-option :value="6" label="已作废" />
-            </el-select>
-          </el-form-item>
-          <el-form-item v-if="activeTab" label="所属部门">
-            <el-select
-              v-model="queryParams.queryParam.departmentName"
-              clearable
-              placeholder="所属部门"
-              style="width: 170px"
-            >
-              <el-option value="院领导">院领导</el-option>
-              <el-option value="所/中心/职能部门/直属研究部/分公司"> 所/中心/职能部门/直属研究部/分公司</el-option>
-              <el-option value="所/中心级部门">所/中心级部门</el-option>
-              <el-option value="科室">科室</el-option>
-              <el-option value="员工">员工</el-option>
-              <el-option value="常驻供应商">常驻供应商</el-option>
-            </el-select>
-          </el-form-item>
-          <el-form-item label="计划日期">
-            <el-date-picker
-              v-model="queryParams.queryParam.date"
-              clearable
-              type="daterange"
-              start-placeholder="开始时间"
-              end-placeholder="结束时间"
-              style="width: 230px"
-            />
-          </el-form-item>
-        </el-form>
+      <el-form ref="formRef" :inline="true" label-width="auto" :model="formValue" :rules="rules">
+        <el-form-item label="项目名称" prop="projectName">
+          <el-input v-model="formValue.projectName" size="large" placeholder="请输入项目名称" style="width: 330px" />
+        </el-form-item>
 
-        <div>
-          <el-button
-            type="primary"
-            @click="
-              $router.push({
-                name: 'createResponsibilityAgree',
-              })
-            "
-            >添加
-          </el-button>
-          <el-button type="primary" @click="queryTableList">查询</el-button>
-          <el-button @click="handleRestParams">重置</el-button>
-        </div>
-      </div>
+        <el-form-item label="施工单位" prop="constructionUnit">
+          <el-input
+            v-model="formValue.constructionUnit"
+            size="large"
+            placeholder="请输入单位全称"
+            style="width: 330px"
+          />
+        </el-form-item>
 
-      <div class="table-content">
-        <el-table :data="tableData.data">
-          <el-table-column label="责任书名称" prop="responsibilityName" width="180" />
-          <el-table-column label="状态" prop="statusName" width="100" />
-          <el-table-column label="所属部门" prop="departmentName" width="180" />
-          <el-table-column label="下发数" prop="issuedQuantity" width="120" />
-          <el-table-column label="签署人数" prop="signedQuantity" width="120" />
-          <el-table-column label="签署比例" prop="signedRatio" width="120" />
-          <el-table-column label="分组名称" prop="userGroupName" width="150" />
-          <el-table-column label="计划完成时间" prop="planEndTime" width="150" />
-          <el-table-column fixed="right" min-width="300" label="操作">
-            <template #default="scope">
-              <el-button
-                v-if="scope.row.status === 1 || scope.row.status === 2 || scope.row.status === 3"
-                type="primary"
-                link
-                @click="
-                  $router.push({
-                    name: 'editResponsibilityAgree',
-                    query: {
-                      id: scope.row.id,
-                    },
-                  })
-                "
-              >
-                编辑
-              </el-button>
-              <!-- <el-button v-if="scope.row.status === 4">审核</el-button> -->
-              <el-popconfirm
-                v-if="scope.row.status === 1 || scope.row.status === 6"
-                title="确定要删除吗?"
-                @confirm="handleConfirmDeleteRow(scope)"
-              >
-                <template #reference>
-                  <el-button type="primary" link>删除</el-button>
-                </template>
-              </el-popconfirm>
-              <el-button
-                v-if="scope.row.status === 1"
-                type="primary"
-                link
-                @click="handleIssueSafetyResponsibility(scope)"
-                >下发
-              </el-button>
-              <!-- <el-popconfirm v-if="scope.row.status === 4">
-                <el-button type="primary" link @click="handleScrap">作废</el-button>
-              </el-popconfirm> -->
+        <el-form-item label="施工地点" prop="constructionLocation">
+          <el-input
+            v-model="formValue.constructionLocation"
+            size="large"
+            placeholder="楼宇名称/区域"
+            style="width: 330px"
+          />
+        </el-form-item>
 
-              <el-button
-                type="primary"
-                link
-                @click="
-                  $router.push({
-                    name: 'viewRecipients',
-                    query: {
-                      id: scope.row.id,
-                      status: scope.row.status,
-                    },
-                  })
-                "
-                >下发对象</el-button
-              >
+        <el-form-item label="施工人数" prop="workerCount">
+          <el-input-number v-model="formValue.workerCount" size="large" :min="1" style="width: 330px" />
+        </el-form-item>
 
-              <el-button type="primary" link @click="handleDownloadLink(scope)">下载</el-button>
-            </template>
-          </el-table-column>
-        </el-table>
-      </div>
-      <div class="pagination-container" v-if="tableData.total > 0">
-        <el-pagination
-          background
-          :current-page="queryParams.pageNumber"
-          :page-size="queryParams.pageSize"
-          :total="tableData.total"
-          @size-change="handleSizeChange"
-          @current-change="handleCurrentChange"
-        />
-      </div>
+        <el-form-item label="项目负责人" prop="projectManagerName">
+          <el-input v-model="formValue.projectManagerName" size="large" placeholder="负责人姓名" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="负责人电话1" prop="projectManagerPhone1">
+          <el-input v-model="formValue.projectManagerPhone1" size="large" placeholder="必填" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="负责人电话2" prop="projectManagerPhone2">
+          <el-input v-model="formValue.projectManagerPhone2" size="large" placeholder="选填" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="现场安全员" prop="siteSafetyManagerName">
+          <el-input
+            v-model="formValue.siteSafetyManagerName"
+            size="large"
+            placeholder="安全员姓名"
+            style="width: 330px"
+          />
+        </el-form-item>
+
+        <el-form-item label="安全员电话1" prop="siteSafetyPhone1">
+          <el-input v-model="formValue.siteSafetyPhone1" size="large" placeholder="必填" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="安全员电话2" prop="siteSafetyPhone2">
+          <el-input v-model="formValue.siteSafetyPhone2" size="large" placeholder="选填" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="开始时间" prop="projectStartTime">
+          <el-date-picker
+            v-model="formValue.projectStartTime"
+            type="date"
+            value-format="YYYY-MM-DD"
+            size="large"
+            style="width: 330px"
+          />
+        </el-form-item>
+
+        <el-form-item label="结束时间" prop="projectEndTime">
+          <el-date-picker
+            v-model="formValue.projectEndTime"
+            type="date"
+            value-format="YYYY-MM-DD"
+            size="large"
+            style="width: 330px"
+          />
+        </el-form-item>
+
+        <el-form-item label="涉及工种" prop="involvedTrades" style="width: 87.2%">
+          <el-checkbox-group v-model="tradeArray">
+            <el-checkbox label="水电" />
+            <el-checkbox label="泥瓦" />
+            <el-checkbox label="木工" />
+            <el-checkbox label="焊接" />
+            <el-checkbox label="气割" />
+            <el-checkbox label="登高" />
+            <el-checkbox label="密闭" />
+            <el-checkbox label="特种驾驶" />
+            <el-checkbox label="其他" />
+          </el-checkbox-group>
+        </el-form-item>
+
+        <el-form-item v-if="tradeArray.includes('其他')" label="其他工种说明" prop="otherTrade">
+          <el-input v-model="formValue.otherTrade" size="large" placeholder="请补充工种类型" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="施工内容简述" prop="constructionContent" style="width: 87.2%">
+          <el-input
+            type="textarea"
+            v-model="formValue.constructionContent"
+            size="large"
+            :rows="3"
+            placeholder="简要描述工程施工内容(方案)"
+          />
+        </el-form-item>
+
+        <el-form-item label="备注说明" prop="otherDescription" style="width: 87.2%">
+          <el-input
+            type="textarea"
+            v-model="formValue.otherDescription"
+            size="large"
+            :rows="3"
+            placeholder="如有其他补充说明请填写"
+          />
+        </el-form-item>
+
+        <div style="width: 100%; margin: 20px 0 10px 12px; font-weight: bold; color: #333">附件上传 (最后显示)</div>
+
+        <el-form-item v-for="item in attachmentConfigs" :key="item.prop" :label="item.label" :prop="item.prop">
+          <UploadFiles
+            :label="item.label"
+            @upload-success="(fileList) => handleUploadSuccess(item.prop, fileList)"
+            :fileList="attachmentLists[item.prop]"
+          />
+        </el-form-item>
+      </el-form>
     </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="$router.back()">返回</el-button>
+      <el-button type="primary" :loading="submiting" @click="handleSubmit">提交</el-button>
+    </footer>
   </div>
-  <IssueSafetyResponsibility
-    v-if="dialogOpen"
-    v-model.visible="dialogOpen"
-    :groupList="groupList"
-    :currentDepartmentKey="currentDepartmentKey"
-    :departmentOptions="departmentOptions"
-    @submit="handleSubmit"
-  />
 </template>
+
 <script lang="ts" setup>
-  import { onMounted, ref, reactive, computed, watch } from 'vue';
-  import dayjs from 'dayjs';
-  import { ElMessage } from 'element-plus';
+  import { ref, reactive, watch } from 'vue';
   import { useRouter } from 'vue-router';
-  import {
-    safetyResponsibilityAdminqueryPage,
-    safetyResponsibilityAdminDelete,
-    safetyResponsibilityAdminIssuedSafety,
-  } from '@/api/production-safety/responsibility-implementation';
-  import { omit } from 'lodash-es';
-  import { queryUserGroupPage } from '@/api/system/person-group';
-  import { formatDeptTree } from '@/views/disaster/utils/formatDeptTree';
-  import { getAllDepartments } from '@/api/auth/dept';
-
-  import { unformatAttachment } from '@/components/UploadFiles/utils';
-  import { downloadFile } from '@/views/disaster/utils';
-  import IssueSafetyResponsibility from './components/IssueSafetyResponsibility.vue';
+  import { ElMessage } from 'element-plus';
+  import dayjs from 'dayjs';
+  // 引入上传组件
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import { constructionSafetySaveConstruction } from '@/api/production-safety/responsibility-implementation';
 
   const router = useRouter();
-  const dialogOpen = ref(false);
-  const issueSafetyResponsibilityRef = ref<any>(null);
-  const currentRowData = ref<any>(null);
-  const activeTab = ref('');
-  const groupList = ref<any[]>([]);
-  const queryParams = reactive<any>({
-    pageNumber: 1,
-    pageSize: 10,
-    queryParam: {
-      responsibilityName: '',
-      departmentName: '',
-      status: '',
-      date: '',
-      responsibilityPersonId: '',
-    },
-  });
-
-  const tableData = reactive({
-    data: [],
-    total: 0,
-  });
-  const deptsOptions = ref<any[]>([]);
+  const formRef = ref<any>(null);
+  const submiting = ref(false);
+  const tradeArray = ref<string[]>([]);
 
-  const filterDeptsByLevel = (data, maxLevel, level = 1) => {
-    if (!data || !Array.isArray(data)) return [];
+  // 附件字段名列表
+  const attachmentConfigs = [
+    { label: '安全交底(必填)', prop: 'safetyCommitmentAttachment' },
+    { label: '安全协议(必填)', prop: 'safetyAgreementAttachment' },
+    { label: '安全告知单(必填)', prop: 'safetyNoticeAttachment' },
+    { label: '施工方案(必填)', prop: 'constructionPlanAttachment' },
+    { label: '劳保清单(必填)', prop: 'ppeListAttachment' },
+    { label: '设备清单(必填)', prop: 'equipmentListAttachment' },
+    { label: '身份信息', prop: 'personnelIdAttachment' },
+    { label: '安全教育记录', prop: 'safetyEducationAttachment' },
+    { label: '环境承诺书', prop: 'environmentCommitmentAttachment' },
+    { label: '消防管理承诺', prop: 'fireManagementAttachment' },
+    { label: '特种作业证', prop: 'specialWorkerCertAttachment' },
+    { label: '特种设备证', prop: 'specialEquipmentCertAttachment' },
+  ];
 
-    return data.map((item) => {
-      const { children, ...rest } = item;
-      if (level < maxLevel && children.length > 0) {
-        return {
-          ...item,
-          children: filterDeptsByLevel(children, maxLevel, level + 1),
-        };
-      }
-      if (level < maxLevel) {
-        return { ...rest, disabled: true };
-      }
-      return rest;
-    });
-  };
+  // 用于 UploadFiles 组件内部维护文件列表的响应式对象
+  const attachmentLists = reactive<any>(Object.fromEntries(attachmentConfigs.map((a) => [a.prop, []])));
 
-  const currentDepartmentKey = computed(() => {
-    switch (currentRowData.value.departmentName) {
-      case '院领导':
-        return 'A';
-      case '所/中心/职能部门/直属研究部/分公司':
-        return 'B';
-      case '所/中心级部门':
-        return 'C';
-      case '科室':
-        return 'D';
-      default:
-        return 'default';
-    }
-  });
-  const departmentOptions = computed(() => {
-    switch (currentDepartmentKey.value) {
-      // 院领导: A
-      case 'B': // 所/中心/职能部门/直属研究部/分公司
-        return filterDeptsByLevel(deptsOptions.value, 1);
-      case 'C': //所/中心级部门
-        return filterDeptsByLevel(deptsOptions.value, 2);
-      case 'D': // 科室
-        return filterDeptsByLevel(deptsOptions.value, 3);
-      default:
-        return deptsOptions.value;
-    }
+  const formValue = reactive<any>({
+    projectName: '',
+    constructionContent: '',
+    constructionLocation: '',
+    constructionUnit: '',
+    projectManagerName: '',
+    projectManagerPhone1: '',
+    projectManagerPhone2: '',
+    siteSafetyManagerName: '',
+    siteSafetyPhone1: '',
+    siteSafetyPhone2: '',
+    workerCount: undefined,
+    projectStartTime: '',
+    projectEndTime: '',
+    involvedTrades: '',
+    otherTrade: '',
+    otherDescription: '',
+    // 接口需要的 String 路径字段
+    ...Object.fromEntries(attachmentConfigs.map((a) => [a.prop, ''])),
   });
 
-  const handleQueryUserGroupPage = () => {
-    return queryUserGroupPage({
-      pageNumber: 1,
-      pageSize: 500,
-    }).then((res) => {
-      groupList.value = res.records;
-    });
+  // 处理附件上传成功回调
+  const handleUploadSuccess = (prop: string, fileList: any[]) => {
+    attachmentLists[prop] = fileList;
+    // 将文件列表转换为 String 存入 formValue 供接口提交
+    // 假设 fileList 里的对象有 url 属性,根据后端需求调整 join 逻辑
+    formValue[prop] = fileList.map((file) => file.url || file.name).join(',');
+    // 触发表单对应字段校验
+    formRef.value?.validateField(prop);
   };
 
-  const getDeptData = () => {
-    return getAllDepartments().then((res) => {
-      deptsOptions.value = formatDeptTree(res);
-    });
-  };
-
-  const handleIssueSafetyResponsibility = (scope) => {
-    currentRowData.value = scope.row;
-    dialogOpen.value = true;
-  };
-
-  const handleSizeChange = (value) => {};
-  const handleCurrentChange = (value) => {
-    queryParams.pageNumber = value;
-    queryTableList();
-  };
+  watch(tradeArray, (val) => {
+    formValue.involvedTrades = val.join(',');
+  });
 
-  const handleDownloadLink = (scope) => {
-    const attachment = unformatAttachment(scope.row.attachment);
-    attachment?.forEach((item: any) => {
-      downloadFile(item.fileUrl, item.fileName);
-    });
-  };
-  const handleConfirmDeleteRow = (scope) => {
-    safetyResponsibilityAdminDelete(scope.row.id).then(() => {
-      ElMessage.success('删除成功!');
-      queryTableList();
-    });
-  };
-  const queryTableList = () => {
-    safetyResponsibilityAdminqueryPage({
-      ...queryParams,
-      queryParam: {
-        ...omit(queryParams.queryParam, 'date'),
-        startTime: queryParams.queryParam.date?.[0]
-          ? dayjs(queryParams.queryParam.date?.[0]).format('YYYY-MM-DD')
-          : undefined,
-        endTime: queryParams.queryParam.date?.[1]
-          ? dayjs(queryParams.queryParam.date?.[1]).format('YYYY-MM-DD')
-          : undefined,
+  const rules = reactive({
+    projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
+    constructionUnit: [{ required: true, message: '请输入施工单位', trigger: 'blur' }],
+    constructionLocation: [{ required: true, message: '请输入施工地点', trigger: 'blur' }],
+    workerCount: [{ required: true, message: '请输入人数', trigger: 'blur' }],
+    projectManagerName: [{ required: true, message: '请输入负责人', trigger: 'blur' }],
+    projectManagerPhone1: [{ required: true, message: '请输入电话', trigger: 'blur' }],
+    siteSafetyManagerName: [{ required: true, message: '请输入安全员', trigger: 'blur' }],
+    siteSafetyPhone1: [{ required: true, message: '请输入电话', trigger: 'blur' }],
+    projectStartTime: [{ required: true, message: '请选择日期', trigger: 'change' }],
+    projectEndTime: [
+      { required: true, message: '请选择日期', trigger: 'change' },
+      {
+        validator: (rule, value, callback) => {
+          if (value && formValue.projectStartTime && dayjs(value).isBefore(dayjs(formValue.projectStartTime))) {
+            callback(new Error('不能早于开始时间'));
+          } else {
+            callback();
+          }
+        },
+        trigger: 'change',
       },
-    }).then((res) => {
-      tableData.data = res.records;
-      tableData.total = res.totalRow;
-    });
-  };
-  const handleRestParams = () => {
-    Object.assign(queryParams, {
-      pageNumber: 1,
-      pageSize: 10,
-      queryParam: {
-        responsibilityName: '',
-        departmentName: '',
-        status: '',
-        date: '',
-        responsibilityPersonId: '',
-      },
-    });
-    queryTableList();
-  };
-
-  const handleSubmit = (formData, submitLoading) => {
-    submitLoading.value = true;
-
-    safetyResponsibilityAdminIssuedSafety({
-      ...formData,
-      adminId: currentRowData.value.id,
-      userGroupId: formData.userGroupId.join(','),
-      deptId: JSON.stringify(formData.deptId),
-      signPerson: formData.signPerson.join(','),
-      departmentId: formData.deptId.map((item) => (Array.isArray(item) ? item[item.length - 1] : item)).join(','),
-    })
-      .then(() => {
-        queryTableList();
-        dialogOpen.value = false;
-        currentRowData.value = {};
-      })
-      .finally(() => {
-        submitLoading.value = false;
-      });
-  };
-  watch(
-    () => activeTab.value,
-    () => {
-      queryParams.queryParam.departmentName = activeTab.value;
-    },
-  );
+    ],
+    involvedTrades: [{ required: true, message: '请选择工种', trigger: 'change' }],
+    constructionContent: [{ required: true, message: '请输入简述', trigger: 'blur' }],
+    // 必填附件校验
+    safetyCommitmentAttachment: [{ required: true, message: '请上传附件', trigger: 'change' }],
+    safetyAgreementAttachment: [{ required: true, message: '请上传附件', trigger: 'change' }],
+    safetyNoticeAttachment: [{ required: true, message: '请上传附件', trigger: 'change' }],
+    constructionPlanAttachment: [{ required: true, message: '请上传附件', trigger: 'change' }],
+    ppeListAttachment: [{ required: true, message: '请上传附件', trigger: 'change' }],
+    equipmentListAttachment: [{ required: true, message: '请上传附件', trigger: 'change' }],
+  });
 
-  onMounted(() => {
-    Promise.all([getDeptData(), handleQueryUserGroupPage()]).then(() => {
-      queryTableList();
+  const handleSubmit = () => {
+    formRef.value?.validate((valid: boolean) => {
+      if (valid) {
+        submiting.value = true;
+        constructionSafetySaveConstruction({ ...formValue })
+          .then(() => {
+            ElMessage.success('提交成功!');
+            router.push({ name: 'areaResponsibilities:public' });
+          })
+          .finally(() => {
+            submiting.value = false;
+          });
+      }
     });
-  });
+  };
 </script>
 
 <style lang="scss" scoped>
-  @use '@/styles/page-details-layout.scss' as *;
   @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/page-details-layout.scss' as *;
   @use '@/styles/basic-table-action.scss' as *;
 
-  :deep(.el-tabs__header) {
-    margin: 0;
-  }
-  :deep(.el-tabs__item) {
-    font-size: 14px !important;
-  }
-  :deep(.flexContent) {
-    display: flex;
-  }
-  :deep(.breadcrumb .title) {
-    margin-left: 0;
+  .safety-platform-container {
+    &__main {
+      padding: 24px;
+      background-color: #fff;
+    }
   }
 
-  :deep(.el-form) {
-    flex: 1;
-    display: flex;
-    row-gap: 15px;
-    flex-wrap: wrap;
-  }
   :deep(.el-form-item) {
-    margin-bottom: 0;
-  }
-  :deep(main) {
-    display: flex;
-    flex-direction: column;
-  }
-  .search-form {
-    min-width: 800px;
-    display: flex;
-
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 20px;
-  }
-
-  .button-content {
-    margin-bottom: 20px;
-  }
-  .table-content {
-    flex: 1;
-    overflow: hidden;
-    overflow-y: auto;
-  }
-  .page-content {
-    display: flex;
-    justify-content: flex-end;
+    margin-right: 18px;
+    margin-bottom: 24px;
+    // 修正 inline 模式下 UploadFiles 的对齐
+    vertical-align: top;
   }
 </style>

+ 2 - 2
src/views/production-safety/implement-safety-duty/responsibility-notice-manage-admin/notice-view.vue

@@ -38,7 +38,7 @@
             </el-select>
           </el-form-item>
 
-          <el-form-item label="所属部门">
+          <el-form-item label="分组名称">
             <el-select v-model="queryParams.queryParam.userGroupId" placeholder="分组名称" style="width: 170px">
               <el-option v-for="group in groupList" :key="group.id" :label="group.name" :value="group.id" />
             </el-select>
@@ -66,7 +66,7 @@
         <el-table :data="tableData.data">
           <el-table-column label="责任书名称" prop="responsibilityName" width="180" />
           <el-table-column label="状态" prop="statusName" width="100" />
-          <el-table-column label="所属部门" prop="departmentName" />
+          <el-table-column label="类别名称" prop="departmentName" />
 
           <el-table-column label="分组名称" prop="userGroupName" />
           <el-table-column label="计划完成时间" prop="planEndTime" />

+ 2 - 2
src/views/production-safety/implement-safety-duty/responsibility-notice-manage-dept/list.vue

@@ -30,11 +30,11 @@
               <el-option :value="5" label="已作废" />
             </el-select>
           </el-form-item>
-          <el-form-item v-if="activeTab" label="所属部门">
+          <el-form-item v-if="activeTab" label="类别名称">
             <el-select
               v-model="queryParams.queryParam.departmentName"
               clearable
-              placeholder="所属部门"
+              placeholder="类别名称"
               style="width: 170px"
             >
               <el-option value="院领导">院领导</el-option>

+ 3 - 3
src/views/production-safety/implement-safety-duty/review-responsibility-agree.vue

@@ -26,11 +26,11 @@
               <el-option :value="6" label="已作废" />
             </el-select>
           </el-form-item>
-          <el-form-item v-if="activeTab" label="所属部门">
+          <el-form-item v-if="activeTab" label="类别名称">
             <el-select
               v-model="queryParams.queryParam.departmentName"
               clearable
-              placeholder="所属部门"
+              placeholder="类别名称"
               style="width: 170px"
             >
               <el-option value="院领导">院领导</el-option>
@@ -74,7 +74,7 @@
           <el-table-column fixed="left" type="selection" width="60" />
           <el-table-column label="责任书名称" prop="responsibilityName" width="180" />
           <el-table-column label="状态" prop="statusName" width="100" />
-          <el-table-column label="所属部门" prop="departmentName" width="180" />
+          <el-table-column label="类别名称" prop="departmentName" width="180" />
           <el-table-column label="分组名称" prop="userGroupName" width="150" />
           <el-table-column label="计划结束时间" prop="planEndTime" width="150" />
           <el-table-column fixed="right" min-width="300" label="操作">

+ 2 - 2
src/views/production-safety/implement-safety-duty/view-recipients.vue

@@ -29,7 +29,7 @@
               <el-option :value="6" label="已作废" />
             </el-select>
           </el-form-item>
-          <el-form-item label="所属部门">
+          <el-form-item label="分组名称">
             <el-select v-model="queryParams.queryParam.userGroupId" placeholder="分组名称" style="width: 170px">
               <el-option v-for="group in groupList" :key="group.id" :label="group.name" :value="group.id" />
             </el-select>
@@ -57,7 +57,7 @@
         <el-table :data="tableData.data">
           <el-table-column label="责任书名称" prop="responsibilityName" width="180" />
           <el-table-column label="状态" prop="statusName" width="100" />
-          <el-table-column label="所属部门" prop="departmentName" />
+          <el-table-column label="类别名称" prop="departmentName" />
 
           <el-table-column label="分组名称" prop="userGroupName" />
           <el-table-column label="计划完成时间" prop="planEndTime" />

+ 295 - 1
src/views/production-safety/risk-identification-and-control/construction-safety-manage/add.vue

@@ -1 +1,295 @@
-<template> xxx </template>
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="breadcrumb-title">
+        <BreadcrumbBack />
+        新增施工安全申请
+      </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <el-form ref="formRef" :inline="true" label-width="auto" :model="formValue" :rules="rules">
+        <el-form-item label="项目名称" prop="projectName">
+          <el-input v-model="formValue.projectName" size="large" placeholder="请输入项目名称" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="施工单位" prop="constructionUnit">
+          <el-input
+            v-model="formValue.constructionUnit"
+            size="large"
+            placeholder="请输入单位全称"
+            style="width: 330px"
+          />
+        </el-form-item>
+
+        <el-form-item label="施工地点" prop="constructionLocation">
+          <el-input
+            v-model="formValue.constructionLocation"
+            size="large"
+            placeholder="楼宇名称/区域"
+            style="width: 330px"
+          />
+        </el-form-item>
+
+        <el-form-item label="施工人数" prop="workerCount">
+          <el-input-number v-model="formValue.workerCount" size="large" :min="1" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="项目负责人" prop="projectManagerName">
+          <el-input v-model="formValue.projectManagerName" size="large" placeholder="负责人姓名" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="负责人电话1" prop="projectManagerPhone1">
+          <el-input v-model="formValue.projectManagerPhone1" size="large" placeholder="必填" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="负责人电话2" prop="projectManagerPhone2">
+          <el-input v-model="formValue.projectManagerPhone2" size="large" placeholder="选填" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="现场安全员" prop="siteSafetyManagerName">
+          <el-input
+            v-model="formValue.siteSafetyManagerName"
+            size="large"
+            placeholder="安全员姓名"
+            style="width: 330px"
+          />
+        </el-form-item>
+
+        <el-form-item label="安全员电话1" prop="siteSafetyPhone1">
+          <el-input v-model="formValue.siteSafetyPhone1" size="large" placeholder="必填" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="安全员电话2" prop="siteSafetyPhone2">
+          <el-input v-model="formValue.siteSafetyPhone2" size="large" placeholder="选填" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="开始时间" prop="projectStartTime">
+          <el-date-picker
+            v-model="formValue.projectStartTime"
+            type="date"
+            value-format="YYYY-MM-DD"
+            size="large"
+            style="width: 330px"
+          />
+        </el-form-item>
+
+        <el-form-item label="结束时间" prop="projectEndTime">
+          <el-date-picker
+            v-model="formValue.projectEndTime"
+            type="date"
+            value-format="YYYY-MM-DD"
+            size="large"
+            style="width: 330px"
+          />
+        </el-form-item>
+
+        <el-form-item label="涉及工种" prop="involvedTrades" style="width: 87.2%">
+          <el-checkbox-group v-model="tradeArray">
+            <el-checkbox label="水电" />
+            <el-checkbox label="泥瓦" />
+            <el-checkbox label="木工" />
+            <el-checkbox label="焊接" />
+            <el-checkbox label="气割" />
+            <el-checkbox label="登高" />
+            <el-checkbox label="密闭" />
+            <el-checkbox label="特种驾驶" />
+            <el-checkbox label="其他" />
+          </el-checkbox-group>
+        </el-form-item>
+
+        <el-form-item v-if="tradeArray.includes('其他')" label="其他工种说明" prop="otherTrade">
+          <el-input v-model="formValue.otherTrade" size="large" placeholder="请补充工种类型" style="width: 330px" />
+        </el-form-item>
+
+        <el-form-item label="施工内容简述" prop="constructionContent" style="width: 87.2%">
+          <el-input
+            type="textarea"
+            v-model="formValue.constructionContent"
+            size="large"
+            :rows="3"
+            placeholder="简要描述施工方案"
+          />
+        </el-form-item>
+
+        <el-form-item label="备注说明" prop="otherDescription" style="width: 87.2%">
+          <el-input
+            type="textarea"
+            v-model="formValue.otherDescription"
+            size="large"
+            :rows="3"
+            placeholder="其他补充说明"
+          />
+        </el-form-item>
+
+        <div style="width: 100%; height: 1px; background: #eee; margin: 20px 0"></div>
+        <div style="width: 100%; margin-bottom: 20px; font-weight: bold; padding-left: 10px"
+          >附件上传清单(全项必填)</div
+        >
+
+        <el-form-item
+          v-for="item in attachmentConfigs"
+          :key="item.prop"
+          :label="item.label"
+          :prop="item.prop"
+          style="width: 43.6%"
+        >
+          <UploadFiles
+            label="上传文件"
+            @upload-success="(fileList) => handleUploadSuccess(item.prop, fileList)"
+            :fileList="attachmentLists[item.prop]"
+          />
+        </el-form-item>
+      </el-form>
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="$router.back()">返回</el-button>
+      <el-button type="primary" :loading="submiting" @click="handleSubmit">保存</el-button>
+      <el-button type="primary" :loading="submiting">提交</el-button>
+    </footer>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, reactive, watch } from 'vue';
+  import { useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import dayjs from 'dayjs';
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import { constructionSafetySaveConstruction } from '@/api/production-safety/responsibility-implementation';
+  import { unformatAttachment, formatAttachmentList } from '@/components/UploadFiles/utils';
+
+  const router = useRouter();
+  const formRef = ref<any>(null);
+  const submiting = ref(false);
+  const tradeArray = ref<string[]>([]);
+
+  // 12个附件全部配置为必填
+  const attachmentConfigs = [
+    { label: '施工安全交底', prop: 'safetyCommitmentAttachment' },
+    { label: '安全管理协议', prop: 'safetyAgreementAttachment' },
+    { label: '安全告知单', prop: 'safetyNoticeAttachment' },
+    { label: '施工方案', prop: 'constructionPlanAttachment' },
+    { label: '劳保用品清单', prop: 'ppeListAttachment' },
+    { label: '施工机械清单', prop: 'equipmentListAttachment' },
+    { label: '人员身份信息', prop: 'personnelIdAttachment' },
+    { label: '安全教育记录', prop: 'safetyEducationAttachment' },
+    { label: '环境承诺书', prop: 'environmentCommitmentAttachment' },
+    { label: '消防管理承诺', prop: 'fireManagementAttachment' },
+    { label: '特种作业证', prop: 'specialWorkerCertAttachment' },
+    { label: '特种设备合格证', prop: 'specialEquipmentCertAttachment' },
+  ];
+
+  const attachmentLists = reactive<any>(Object.fromEntries(attachmentConfigs.map((a) => [a.prop, []])));
+
+  const formValue = reactive<any>({
+    projectName: '',
+    constructionContent: '',
+    constructionLocation: '',
+    constructionUnit: '',
+    projectManagerName: '',
+    projectManagerPhone1: '',
+    projectManagerPhone2: '',
+    siteSafetyManagerName: '',
+    siteSafetyPhone1: '',
+    siteSafetyPhone2: '',
+    workerCount: undefined,
+    projectStartTime: '',
+    projectEndTime: '',
+    involvedTrades: '',
+    otherTrade: '',
+    otherDescription: '',
+    ...Object.fromEntries(attachmentConfigs.map((a) => [a.prop, ''])),
+  });
+
+  const handleUploadSuccess = async (prop: string, fileList: any[]) => {
+    attachmentLists[prop] = fileList;
+    // 后端接口通常需要字符串路径,这里将 file 数组转成字符串
+    // formValue[prop] = fileList;
+    formValue[prop] = JSON.stringify(await formatAttachmentList(fileList));
+    formRef.value?.validateField(prop);
+  };
+
+  watch(tradeArray, (val) => {
+    formValue.involvedTrades = val.join(',');
+  });
+
+  // 定义全字段规则
+  const rules = reactive({
+    projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
+    constructionUnit: [{ required: true, message: '请输入施工单位', trigger: 'blur' }],
+    constructionLocation: [{ required: true, message: '请输入施工地点', trigger: 'blur' }],
+    workerCount: [{ required: true, message: '请输入人数', trigger: 'blur' }],
+    projectManagerName: [{ required: true, message: '请输入项目负责人', trigger: 'blur' }],
+    projectManagerPhone1: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
+    siteSafetyManagerName: [{ required: true, message: '请输入安全负责人', trigger: 'blur' }],
+    siteSafetyPhone1: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
+    projectStartTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
+    projectEndTime: [
+      { required: true, message: '请选择结束时间', trigger: 'change' },
+      {
+        validator: (rule, value, callback) => {
+          if (value && formValue.projectStartTime && dayjs(value).isBefore(dayjs(formValue.projectStartTime))) {
+            callback(new Error('不能早于开始时间'));
+          } else {
+            callback();
+          }
+        },
+        trigger: 'change',
+      },
+    ],
+    involvedTrades: [{ required: true, message: '请选择工种', trigger: 'change' }],
+    constructionContent: [{ required: true, message: '请输入简述', trigger: 'blur' }],
+    // 动态为 12 个附件添加必填校验
+    ...Object.fromEntries(
+      attachmentConfigs.map((a) => [a.prop, [{ required: true, message: '请上传' + a.label, trigger: 'change' }]]),
+    ),
+  });
+
+  const handleSubmit = () => {
+    formRef.value?.validate(async (valid: boolean) => {
+      if (valid) {
+        const postData = { ...formValue };
+        submiting.value = true;
+
+        // for (const item of attachmentConfigs) {
+        //   const rawFileList = attachmentLists[item.prop];
+        //   const formatted = await formatAttachmentList(rawFileList);
+        //   postData[item.prop] = JSON.stringify(formatted);
+        // }
+        constructionSafetySaveConstruction(postData)
+          .then(() => {
+            ElMessage.success('保存成功!');
+            router.push({ name: 'constructionSafetyManage' });
+          })
+          .finally(() => {
+            submiting.value = false;
+          });
+      } else {
+      }
+    });
+  };
+</script>
+
+<style lang="scss" scoped>
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/page-details-layout.scss' as *;
+
+  .safety-platform-container {
+    &__main {
+      padding: 24px;
+      background-color: #fff;
+    }
+
+    // 深度调整封装组件在 inline 模式下的表现
+    :deep(.el-form-item) {
+      margin-right: 20px;
+      margin-bottom: 24px;
+      vertical-align: top;
+
+      // 让附件标签宽度更有呼吸感
+      .el-form-item__label {
+        font-weight: bold;
+      }
+    }
+  }
+</style>

+ 1 - 1
src/views/production-safety/risk-identification-and-control/construction-safety-manage/list.vue

@@ -40,7 +40,7 @@
         </el-form>
 
         <div>
-          <el-button type="primary" @click="$router.push({ name: 'hazardManageAdd' })">添加 </el-button>
+          <el-button type="primary" @click="$router.push({ name: 'constructionSafetyManageAdd' })">添加 </el-button>
           <el-button type="primary" @click="queryTableList">查询</el-button>
           <el-button @click="handleRestParams">重置</el-button>
         </div>

+ 19 - 25
src/views/production-safety/safetyAssessment/evaluationSystem/evaluationSystem.vue

@@ -7,9 +7,7 @@
       <div class="search-table-container">
         <header>
           <div style="position: relative">
-            <el-button type="primary" class="search-table-container--button" @click="handleCreate">
-              添加
-            </el-button>
+            <el-button type="primary" class="search-table-container--button" @click="handleCreate"> 添加 </el-button>
           </div>
 
           <div class="act-search">
@@ -24,11 +22,7 @@
               </div>
               <div class="select-box--item">
                 <span>状态:</span>
-                <el-select
-                  v-model="tableQuery.queryParam.status"
-                  placeholder="请选择状态"
-                  clearable
-                >
+                <el-select v-model="tableQuery.queryParam.status" placeholder="请选择状态" clearable>
                   <el-option
                     v-for="item in EVALUATION_SYSTEM_STATUS_OPTIONS"
                     :key="item.value"
@@ -215,12 +209,7 @@
               clearable
               style="width: 100%"
             >
-              <el-option
-                v-for="item in userGroupOptions"
-                :key="item.id"
-                :label="item.name"
-                :value="item.id"
-              />
+              <el-option v-for="item in userGroupOptions" :key="item.id" :label="item.name" :value="item.id" />
             </el-select>
           </el-form-item>
           <el-form-item label="部门自评审核人:">
@@ -433,9 +422,10 @@
           feedbackCount: item.replyNum, // 反馈数(接口未返回,暂时设为0)
           feedbackRatio: item.replyRate, // 反馈比例(接口未返回,暂时设为0%)
           evaluationDocument: item.attachments, // 考核文档
-          plannedCompletionTime: item.planStartTime && item.planEndTime
-            ? `${item.planStartTime} 至 ${item.planEndTime}`
-            : item.planStartTime || item.planEndTime || '-', // 计划完成时间
+          plannedCompletionTime:
+            item.planStartTime && item.planEndTime
+              ? `${item.planStartTime} 至 ${item.planEndTime}`
+              : item.planStartTime || item.planEndTime || '-', // 计划完成时间
         }));
         pagination.total = res.totalRow;
       }
@@ -513,16 +503,15 @@
     issueDeptIds.value = [];
     issueDialogVisible.value = true;
     // 弹窗打开时才加载下发弹窗所需数据
-    await Promise.all([
-      getDeptTreeData(),
-      getUserGroupOptions(),
-      getDeptSelfApproveUserList(),
-    ]);
+    await Promise.all([getDeptTreeData(), getUserGroupOptions(), getDeptSelfApproveUserList()]);
   };
 
   const handleIssueDeptChange = () => {
     const nodes = issueDeptCascaderRef.value?.getCheckedNodes?.() ?? [];
-    issueForm.departmentName = nodes.map((n: { label?: string; pathLabels?: string[] }) => n.pathLabels?.join('/') || n.label || '').filter(Boolean).join(',');
+    issueForm.departmentName = nodes
+      .map((n: { label?: string; pathLabels?: string[] }) => n.pathLabels?.join('/') || n.label || '')
+      .filter(Boolean)
+      .join(',');
   };
 
   const handleIssueConfirm = async () => {
@@ -653,7 +642,9 @@
   };
 
   // 解析逗号分隔的URL字符串为文件列表
-  const parseAttachments = (attachmentsStr: string | undefined): Array<{
+  const parseAttachments = (
+    attachmentsStr: string | undefined,
+  ): Array<{
     fileUrl: string;
     fileName: string;
     fileType: string;
@@ -663,7 +654,10 @@
     }
 
     // 按逗号分割URL
-    const urls = attachmentsStr.split(',').map(url => url.trim()).filter(url => url);
+    const urls = attachmentsStr
+      .split(',')
+      .map((url) => url.trim())
+      .filter((url) => url);
 
     return urls.map((url) => {
       // 从URL中提取文件名