sunqijun 3 месяцев назад
Родитель
Сommit
064506493c

+ 11 - 0
src/api/production-safety/responsibility-implementation/index.ts

@@ -803,3 +803,14 @@ export function constructionSafetySaveConstruction(params) {
     params
   });
 }
+/**
+ * 管理端查询施工安全详情
+ * @param id - 施工安全申请 ID
+ * @returns Promise<any> 施工安全申请详细信息
+ */
+export function constructionSafetyQueryDetailConstruction(id) {
+  return http.request({
+    url: `/constructionSafety/queryDetailConstruction?id=${id}`,
+    method: 'get',
+  });
+}

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

@@ -1,279 +1,411 @@
 <template>
   <div class="safety-platform-container">
     <header class="safety-platform-container__header">
-      <div class="breadcrumb-title">
-        <BreadcrumbBack />
-        新增施工作业安全
-      </div>
+      <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>
     </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>
+      <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-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>
+        <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="otherDescription" style="width: 87.2%">
-          <el-input
-            type="textarea"
-            v-model="formValue.otherDescription"
-            size="large"
-            :rows="3"
-            placeholder="如有其他补充说明请填写"
-          />
-        </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> -->
 
-        <div style="width: 100%; margin: 20px 0 10px 12px; font-weight: bold; color: #333">附件上传 (最后显示)</div>
+              <el-button
+                type="primary"
+                link
+                @click="
+                  $router.push({
+                    name: 'viewRecipients',
+                    query: {
+                      id: scope.row.id,
+                      status: scope.row.status,
+                    },
+                  })
+                "
+                >下发对象</el-button
+              >
 
-        <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>
+              <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>
     </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 { ref, reactive, watch } from 'vue';
-  import { useRouter } from 'vue-router';
-  import { ElMessage } from 'element-plus';
+  import { onMounted, ref, reactive, computed, watch } from 'vue';
   import dayjs from 'dayjs';
-  // 引入上传组件
-  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
-  import { constructionSafetySaveConstruction } from '@/api/production-safety/responsibility-implementation';
+  import { ElMessage } from 'element-plus';
+  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';
 
   const router = useRouter();
-  const formRef = ref<any>(null);
-  const submiting = ref(false);
-  const tradeArray = ref<string[]>([]);
+  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 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 filterDeptsByLevel = (data, maxLevel, level = 1) => {
+    if (!data || !Array.isArray(data)) return [];
 
-  // 用于 UploadFiles 组件内部维护文件列表的响应式对象
-  const attachmentLists = reactive<any>(Object.fromEntries(attachmentConfigs.map((a) => [a.prop, []])));
+    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;
+    });
+  };
 
-  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 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 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 handleQueryUserGroupPage = () => {
+    return queryUserGroupPage({
+      pageNumber: 1,
+      pageSize: 500,
+    }).then((res) => {
+      groupList.value = res.records;
+    });
   };
 
-  watch(tradeArray, (val) => {
-    formValue.involvedTrades = val.join(',');
-  });
+  const getDeptData = () => {
+    return getAllDepartments().then((res) => {
+      deptsOptions.value = formatDeptTree(res);
+    });
+  };
 
-  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' }],
-    // 必填附件校验
-    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' }],
-  });
+  const handleIssueSafetyResponsibility = (scope) => {
+    currentRowData.value = scope.row;
+    dialogOpen.value = true;
+  };
 
-  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;
-          });
-      }
+  const handleSizeChange = (value) => {};
+  const handleCurrentChange = (value) => {
+    queryParams.pageNumber = value;
+    queryTableList();
+  };
+
+  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,
+      },
+    }).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;
+    },
+  );
+
+  onMounted(() => {
+    Promise.all([getDeptData(), handleQueryUserGroupPage()]).then(() => {
+      queryTableList();
+    });
+  });
 </script>
 
 <style lang="scss" scoped>
-  @use '@/styles/page-main-layout.scss' as *;
   @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
   @use '@/styles/basic-table-action.scss' as *;
 
-  .safety-platform-container {
-    &__main {
-      padding: 24px;
-      background-color: #fff;
-    }
+  :deep(.el-tabs__header) {
+    margin: 0;
+  }
+  :deep(.el-tabs__item) {
+    font-size: 14px !important;
+  }
+  :deep(.flexContent) {
+    display: flex;
+  }
+  :deep(.breadcrumb .title) {
+    margin-left: 0;
   }
 
+  :deep(.el-form) {
+    flex: 1;
+    display: flex;
+    row-gap: 15px;
+    flex-wrap: wrap;
+  }
   :deep(.el-form-item) {
-    margin-right: 18px;
-    margin-bottom: 24px;
-    // 修正 inline 模式下 UploadFiles 的对齐
-    vertical-align: top;
+    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;
   }
 </style>

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

@@ -1 +1,318 @@
-<template> xxx </template>
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="breadcrumb-title">
+        <BreadcrumbBack />
+        {{ pageTitle }}
+      </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <el-form ref="formRef" :inline="true" label-width="auto" :model="formValue" :rules="rules" :disabled="isView">
+        <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
+              v-for="t in ['水电', '泥瓦', '木工', '焊接', '气割', '登高', '密闭', '特种驾驶', '其他']"
+              :key="t"
+              :label="t"
+            />
+          </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
+            :disabled="isView"
+            @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 v-if="!isView" type="primary" :loading="submiting" @click="handleSubmit">保存</el-button>
+    </footer>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, reactive, watch, onMounted, computed } from 'vue';
+  import { useRouter, useRoute } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import dayjs from 'dayjs';
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import {
+    constructionSafetySaveConstruction,
+    constructionSafetyQueryDetailConstruction,
+  } from '@/api/production-safety/responsibility-implementation';
+  import { unformatAttachment, formatAttachmentList } from '@/components/UploadFiles/utils';
+
+  const router = useRouter();
+  const route = useRoute();
+  const formRef = ref<any>(null);
+  const submiting = ref(false);
+  const tradeArray = ref<string[]>([]);
+
+  // 页面模式逻辑
+  const pageType = computed(() => route.query.type as string); // 'view', 'edit', 'add'
+  const businessId = computed(() => route.query.id as string);
+  const isView = computed(() => pageType.value === 'view');
+  const pageTitle = computed(() => {
+    if (isView.value) return '查看施工安全申请';
+    return businessId.value ? '编辑施工安全申请' : '新增施工安全申请';
+  });
+
+  // 附件项定义
+  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>({
+    id: undefined,
+    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 initDetail = async () => {
+    if (!businessId.value) return;
+    try {
+      const res = await constructionSafetyQueryDetailConstruction(route.query.id);
+      if (res?.data) {
+        Object.assign(formValue, res.data);
+        // 处理多选框回显
+        tradeArray.value = res.data.involvedTrades ? res.data.involvedTrades.split(',') : [];
+        // 处理附件回显
+        attachmentConfigs.forEach((item) => {
+          if (res.data[item.prop]) {
+            attachmentLists[item.prop] = unformatAttachment(res.data[item.prop]);
+          }
+        });
+      }
+    } catch (error) {
+      console.error('Detail fetch error:', error);
+    }
+  };
+
+  onMounted(initDetail);
+
+  // 附件变更处理
+  const handleUploadSuccess = async (prop: string, fileList: any[]) => {
+    attachmentLists[prop] = fileList;
+    // 直接转成 JSON 字符串存入表单对象,确保提交时格式正确
+    const formatted = await formatAttachmentList(fileList);
+    formValue[prop] = formatted && formatted.length > 0 ? JSON.stringify(formatted) : '';
+    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' }],
+    ...Object.fromEntries(
+      attachmentConfigs.map((a) => [a.prop, [{ required: true, message: `请上传${a.label}`, trigger: 'change' }]]),
+    ),
+  });
+
+  const handleSubmit = () => {
+    formRef.value?.validate((valid: boolean) => {
+      if (valid) {
+        submiting.value = true;
+        constructionSafetySaveConstruction(formValue)
+          .then(() => {
+            ElMessage.success('保存成功!');
+            router.push({ name: 'constructionSafetyManage' });
+          })
+          .finally(() => {
+            submiting.value = false;
+          });
+      }
+    });
+  };
+</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>