ソースを参照

feat:区域检查计划任务部门列表

sunqijun 3 週間 前
コミット
fcd2e68097

+ 307 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTaskDept/areaCheckPlanTaskDept.vue

@@ -0,0 +1,307 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="breadcrumb-title">区域检查计划任务(管理员)</div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header>
+          <!-- <div style="position: relative">
+            <el-button plain class="search-table-container--button" @click="handleImport"> 导入 </el-button>
+          </div> -->
+
+          <div class="act-search">
+            <section class="select-box">
+              <div class="select-box--item">
+                <span>检查场所/检查人员:</span>
+                <el-input
+                  v-model="tableQuery.queryParam.searchKey"
+                  placeholder="搜索检查场所或检查人员"
+                  class="act-search-input"
+                  style="min-width: 200px;"
+                  clearable
+                />
+              </div>
+              <div class="select-box--item">
+                <span>状态:</span>
+                <el-select v-model="tableQuery.queryParam.status" placeholder="请选择状态" clearable>
+                  <el-option
+                    v-for="item in AREA_CHECK_PLAN_STATUS_OPTIONS"
+                    :key="String(item.value)"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-select>
+              </div>
+              <div class="select-box--item">
+                <span>日期范围:</span>
+                <el-date-picker
+                  v-model="dateRange"
+                  type="daterange"
+                  range-separator="-"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                  value-format="YYYY-MM-DD"
+                  format="YYYY-MM-DD"
+                />
+              </div>
+            </section>
+            <section class="search-btn">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+              <el-button plain @click="handleDownload"> 导出 </el-button>
+            </section>
+          </div>
+        </header>
+
+        <div class="batch-table">
+          <BasicTable
+            ref="basicTableRef"
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:pageSize="handleSizeChange"
+            @update:pageNumber="handleCurrentChange"
+          >
+            <template #status="scope">
+              <span>{{ AREA_CHECK_PLAN_STATUS_LABEL[String(scope.row.status)] ?? '-' }}</span>
+            </template>
+            <template #needOverallDesc="scope">
+              <span>{{
+               scope.row.status === 0 ? '-' : scope.row.needOverallDesc === true ? '是' : scope.row.needOverallDesc === false ? '否' : '-'
+              }}</span>
+            </template>
+            <template #sign="scope">
+            <div class="file-list">
+                  <div class="file-item" v-for="item in scope.row.checkedPersonSign" :key="item.fileId">
+                    <span class="file-item--name">{{ item.fileName }}</span>
+                    <div class="file-item--footer">
+                      <el-button link type="primary" @click="previewOnline(item.fileUrl, item.fileType)"
+                        >预览</el-button
+                      >
+                      <el-button link type="primary" @click.stop="downloadFile(item.fileUrl, item.fileName)"
+                        >下载</el-button
+                      >
+                    </div>
+                  </div>
+                </div>
+            </template>
+            <template #needSigneeSign="scope">
+              <span>{{
+                scope.row.status === 0 ? '-' : scope.row.needSigneeSign === true ? '是' : scope.row.needSigneeSign === false ? '否' : '-'
+              }}</span>
+            </template>
+            <template #startDate="scope">
+              <span>{{ scope.row.startDate ?? '-' }}</span>
+            </template>
+            <template #endDate="scope">
+              <span>{{ scope.row.endDate ?? '-' }}</span>
+            </template>
+            <template #action="scope">
+              <div class="action-container--div" style="justify-content: left">
+                <template v-if="Number(scope.row.status) === 0">
+                  <ActionButton text="查看" @click="handleView(scope.row.id)" />
+                </template>
+              </div>
+            </template>
+          </BasicTable>
+        </div>
+      </div>
+    </main>
+
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, reactive, ref } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import BasicTable from '@/components/BasicTable.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import ActionButton from '@/components/ActionButton.vue';
+  import { Plus } from '@element-plus/icons-vue';
+  import {
+    TABLE_OPTIONS,
+    AREA_CHECK_PLAN_TABLE_COLUMNS,
+    AREA_CHECK_PLAN_STATUS_OPTIONS,
+    AREA_CHECK_PLAN_STATUS_LABEL,
+  } from './configs/tables';
+  import { useRouter } from 'vue-router';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import type { AreaCheckPlanQuery, AreaCheckPlanRecord } from './configs/types';
+  import {
+    queryAreaCheckPlanDetailPage,
+    mapAreaCheckPlanApiRecordToUi,
+    exportAreaCheckPlanAdministration,
+  } from '@/api/production-safety-system';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import type { DeptTree } from '@/types/dept/type';
+  import { queryUserGroupPage } from '@/api/system/person-group';
+  import type { PersonGroupListItem } from '@/types/person-group/type';
+  import BatchImport from '@/components/batch-import/BatchImport.vue';
+  import { useGlobSetting } from '@/hooks/setting';
+  import urlJoin from 'url-join';
+  import { downloadByData } from '@/utils/file/download';
+  import { downloadFile } from '@/views/disaster/utils';
+  const router = useRouter();
+
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+  const { tableConfig, pagination } = useTableConfig(AREA_CHECK_PLAN_TABLE_COLUMNS, TABLE_OPTIONS);
+  const tableData = ref<AreaCheckPlanRecord[]>([]);
+  const dateRange = ref<[string, string] | null>(null);
+
+  // 场所所属类别选项(示例数据,本页面不调用接口)
+  const venueCategoryOptions = ref<Array<{ label: string; value: string }>>([]);
+
+
+
+
+
+  const previewOnline = (url: string | undefined, type) => {
+    if (url) {
+      previewOnlineRef.value?.open(url, type);
+    }
+  };
+
+
+
+
+
+
+
+  const tableQuery = reactive<QueryPageRequest<AreaCheckPlanQuery>>({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {
+      searchKey: '',
+      status: '' as AreaCheckPlanQuery['status'],
+      venueCategory: '',
+      startDate: '',
+      endDate: '',
+    },
+  });
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    tableQuery.pageSize = value;
+    getTableData();
+  };
+
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tableQuery.pageNumber = value;
+    getTableData();
+  };
+
+  async function getTableData() {
+    tableConfig.loading = true;
+    try {
+      const res = await queryAreaCheckPlanDetailPage({
+        pageNumber: tableQuery.pageNumber,
+        pageSize: tableQuery.pageSize,
+        queryParam: tableQuery.queryParam,
+      });
+      const raw = (res as { data?: { records?: unknown[]; totalRow?: number } })?.data ?? res;
+      const list = raw?.records ?? [];
+      tableData.value = list.map((r: unknown) => mapAreaCheckPlanApiRecordToUi(r));
+      pagination.total = raw?.totalRow ?? 0;
+    } catch (e) {
+      console.error('查询区域检查计划列表失败:', e);
+      tableData.value = [];
+      pagination.total = 0;
+    } finally {
+      tableConfig.loading = false;
+    }
+  }
+
+  const handleSearch = () => {
+    if (dateRange.value && dateRange.value.length === 2) {
+      tableQuery.queryParam.startDate = dateRange.value[0];
+      tableQuery.queryParam.endDate = dateRange.value[1];
+    } else {
+      tableQuery.queryParam.startDate = '';
+      tableQuery.queryParam.endDate = '';
+    }
+    pagination.pageNumber = 1;
+    tableQuery.pageNumber = 1;
+    getTableData();
+  };
+
+  const handleReset = () => {
+    tableQuery.queryParam.searchKey = '';
+    tableQuery.queryParam.status = '';
+    tableQuery.queryParam.venueCategory = '';
+    tableQuery.queryParam.startDate = '';
+    tableQuery.queryParam.endDate = '';
+    dateRange.value = null;
+    handleSearch();
+  };
+
+
+
+  const handleView = (id: number) => {
+    router.push({
+      name: 'areaCheckPlanTaskItem',
+      query: { id, operate: 'area-check-plan-view' },
+    });
+  };
+
+
+
+
+  const handleDownload = async () => {
+    try {
+      const response = await exportAreaCheckPlanAdministration(tableQuery.queryParam);
+      if (response) {
+        const fileName = `区域检查计划任务_${new Date().toISOString().split('T')[0]}.xlsx`;
+        downloadByData(response, fileName);
+        ElMessage.success('导出成功');
+      }
+    } catch (e) {
+      console.error('导出文件失败:', e);
+      ElMessage.error('导出失败,请重试');
+    }
+  };
+
+  onMounted(() => {
+    venueCategoryOptions.value = [
+      { label: '各级风险点', value: '各级风险点' },
+      { label: '关键业务活动', value: '关键业务活动' },
+      { label: '日常安全', value: '日常安全' },
+      { label: '各级危险点', value: '各级危险点' },
+      { label: '试验室及试验过程', value: '试验室及试验过程' },
+      {
+        label: '办公区域(含地下车库、图书馆、档案库房、仓库等)',
+        value: '办公区域(含地下车库、图书馆、档案库房、仓库等)',
+      },
+      { label: '老旧厂房', value: '老旧厂房' },
+      { label: '施工现场', value: '施工现场' },
+      { label: '职工食堂', value: '职工食堂' },
+      { label: '职工宿舍', value: '职工宿舍' },
+      { label: '体育活动场所', value: '体育活动场所' },
+      { label: '托育园', value: '托育园' },
+      { label: '租、出借房屋', value: '租、出借房屋' },
+      { label: '院内经营服务场所', value: '院内经营服务场所' },
+      { label: '垃圾房', value: '垃圾房' },
+      { label: '院内交通', value: '院内交通' },
+      { label: '消防设施设备', value: '消防设施设备' },
+      { label: '特种设备', value: '特种设备' },
+      { label: '防雷设施', value: '防雷设施' },
+      { label: '供、配电设施设备(含弱电)', value: '供、配电设施设备(含弱电)' },
+      { label: '公务车辆', value: '公务车辆' },
+      { label: '燃气管道设施(含报警装置)', value: '燃气管道设施(含报警装置)' },
+      { label: '建筑物外墙标识物、装饰物', value: '建筑物外墙标识物、装饰物' },
+      { label: 'UPS电源', value: 'UPS电源' },
+      { label: '危险化学品', value: '危险化学品' },
+      { label: '设施设备应急操作流程', value: '设施设备应急操作流程' },
+      { label: '堆场、物资库房', value: '堆场、物资库房' },
+      { label: '室内外停车场', value: '室内外停车场' },
+    ];
+    getTableData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  @use '@/views/traffic/violation/style/act-search-table.scss' as *;
+</style>

+ 46 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTaskDept/areaCheckPlanTaskDeptItem.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ headerTitle }}</span>
+    </header>
+
+    <AreaCheckPlanRecordDetail />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed } from 'vue';
+  import { useRoute } from 'vue-router';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+
+  import AreaCheckPlanRecordDetail from './components/areaCheckPlanRecordDetail.vue';
+
+  const route = useRoute();
+  const operate = computed(() => (route.query.operate as string) || '');
+
+  const headerTitle = computed(() => {
+    switch (operate.value) {
+      case 'area-check-plan-create':
+        return '开始任务检查';
+      case 'area-check-plan-edit':
+        return '编辑任务检查';
+      case 'area-check-plan-view':
+        return '查看检查任务信息';
+      default:
+        return '未知操作';
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+</style>
+

+ 396 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTaskDept/components/areaCheckPlanRecordDetail.vue

@@ -0,0 +1,396 @@
+<template>
+  <main class="safety-platform-container__main">
+    <el-form ref="formRef" :model="formData" :rules="isViewMode ? undefined : formRules" label-width="180px" class="check-record-form">
+      <el-form-item label="被检查单位:" prop="inspectedUnit">
+        <el-input v-model="formData.inspectedUnit" :disabled="!isAddMode" placeholder="请输入被检查单位" clearable />
+      </el-form-item>
+      <el-form-item label="检查人员:" prop="inspector">
+        <el-input v-model="formData.inspector" :disabled="!isAddMode" placeholder="请输入检查人员" clearable />
+      </el-form-item>
+      <el-form-item label="检查时间:" prop="checkTime">
+        <el-date-picker
+          v-if="isAddMode"
+          v-model="formData.checkTime"
+          type="datetime"
+          placeholder="选择检查日期"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          format="YYYY-MM-DD HH:mm:ss"
+          style="width: 100%"
+        />
+        <el-input v-else v-model="formData.checkTime" disabled />
+      </el-form-item>
+      <el-form-item label="检查地点:" prop="checkPlace">
+        <el-input v-model="formData.checkPlace" :disabled="!isAddMode" placeholder="请输入检查地点" clearable />
+      </el-form-item>
+      <el-form-item v-if="needOverallDesc === '1'" label="整体检查情况描述:" prop="overallDesc" :required="needOverallDesc === '1' && isAddMode">
+        <el-input
+          v-model="formData.overallDesc"
+          type="textarea"
+          :rows="5"
+          maxlength="300"
+          show-word-limit
+          :disabled="!isAddMode"
+          :placeholder="isAddMode ? '请输入整体检查情况描述(限300字)' : '整体检查情况描述(限300字)'"
+        />
+      </el-form-item>
+      <el-form-item v-if="needSigneeSign === '1'" label="被检查人签字文件:" prop="signFile" :required="needSigneeSign === '1' && isAddMode">
+        <UploadFiles
+          v-if="!isViewMode"
+          label="上传文件"
+          :maxCount="1"
+          :file-list="signFileList"
+          :disabled="isViewMode"
+          :allow-all-file-types="true"
+          :accept="'.jpg,.jpeg,.png'"
+          :desc="'支持.jpg,.jpeg,.png'"
+          @uploadSuccess="(list: FileItem[]) => handleUploadSuccess(list)"
+        />
+        <div class="file-list" v-else>
+          <div class="file-item" v-for="file in signFileList" :key="file.fileId">
+            <span class="file-item--name">{{ file.fileName }}</span>
+            <div class="file-item--footer">
+              <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
+                >预览</el-button
+              >
+              <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
+                >下载</el-button
+              >
+            </div>
+          </div>
+        </div>
+      </el-form-item>
+    </el-form>
+
+    <div class="check-items-section">
+      <div class="section-header">
+        <span class="section-title">检查明细</span>
+      </div>
+      <el-table :data="checkItems" border class="check-items-table">
+        <el-table-column type="index" label="编号" width="80" align="center" />
+        <el-table-column prop="checkContent" label="检查内容" min-width="200" />
+        <el-table-column prop="checkStandard" label="检查标准" min-width="200" />
+        <el-table-column label="检查结果" width="160" align="center">
+          <template #default="{ row }">
+            <el-radio-group v-if="isAddMode" v-model="row.checkResult" size="small">
+              <el-radio value="合格">合格</el-radio>
+              <el-radio value="不合格">不合格</el-radio>
+              <!-- <el-radio value="待检">待检</el-radio> -->
+            </el-radio-group>
+            <span v-else>{{ row.checkResult || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="检查发现问题" min-width="200">
+          <template #default="{ row }">
+            <el-input v-if="isAddMode" v-model="row.checkProblem" placeholder="请输入检查发现问题" clearable  :disabled="row.checkResult === '不合格' ? false : true"/>
+            <span v-else>{{ row.checkProblem || '-' }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <footer v-if="isAddMode" class="safety-platform-container__footer">
+      <el-button @click="handleBack">返回</el-button>
+      <el-button type="primary" @click="handleSubmit">提交</el-button>
+    </footer>
+
+    <PreviewOnline ref="previewOnlineRef" />
+
+  </main>
+</template>
+
+<script setup lang="ts">
+  import { computed, onMounted, ref } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import type { FileItem } from '@/components/UploadFiles/types';
+  import {
+    queryAreaCheckPlanManageDeptDetail,
+    queryAreaCheckRecordDept,
+    mapAreaCheckPlanApiRecordToUi,
+    queryAreaCheckRecordTemplateDept,
+    saveAreaCheckPlanDetailDept,
+    type AreaCheckRecordTemplateItem,
+  } from '@/api/production-safety-system';
+  import { formatAttachmentList } from '@/components/UploadFiles/utils';
+  import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
+  import { downloadFile } from '@/views/disaster/utils';
+  import { useUserInfoHook } from '@/views/disaster/hooks';
+
+  const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
+  const router = useRouter();
+  const route = useRoute();
+  const { id } = useUserInfoHook()
+
+  const operate = computed(() => (route.query.operate as string) || '');
+  const isAddMode = computed(() => operate.value === 'area-check-plan-record-add');
+  const isViewMode = computed(() => operate.value === 'area-check-plan-record-view');
+  const recordId = computed(() => Number(route.query.recordId));
+  const planId = computed(() => Number(route.query.planId));
+  const needOverallDesc = computed(() => route.query.needOverallDesc);
+  const needSigneeSign = computed(() => route.query.needSigneeSign);
+  const formRef = ref();
+  const formData = ref({
+    inspectedUnit: '',
+    inspector: '',
+    checkTime: '',
+    checkPlace: '',
+    overallDesc: '',
+    signFile: '',
+  });
+
+  const formRules = {
+    inspectedUnit: [{ required: true, message: '请输入被检查单位', trigger: 'blur' }],
+    inspector: [{ required: true, message: '请输入检查人员', trigger: 'blur' }],
+    checkTime: [{ required: true, message: '请选择检查时间', trigger: 'change' }],
+    checkPlace: [{ required: true, message: '请输入检查地点', trigger: 'blur' }],
+    overallDesc: [{ 
+      required: needOverallDesc.value === '1', 
+      message: '请输入整体检查情况描述', 
+      trigger: 'blur' 
+    }],
+    signFile: [{ 
+      required: needSigneeSign.value === '1', 
+      message: '请上传被检查人签字文件', 
+      trigger: 'change' 
+    }],
+  };
+
+  interface CheckItem {
+    checkContent: string;
+    checkStandard: string;
+    checkResult: string;
+    checkProblem: string;
+  }
+
+  const checkItems = ref<CheckItem[]>([]);
+
+  function convertSignFileToFileItems(attachmentsStr: string): FileItem[] {
+    if (!attachmentsStr || !String(attachmentsStr).trim()) return [];
+    const urls = String(attachmentsStr)
+      .split(',')
+      .map((u) => u.trim())
+      .filter(Boolean);
+    return urls.map((url, index) => {
+      const parts = url.split('/');
+      const fileName = parts[parts.length - 1] || `附件${index + 1}`;
+      const ext = fileName.split('.').pop()?.toLowerCase() || '';
+      let fileType = 'pdf';
+      if (['doc', 'docx'].includes(ext)) fileType = 'word';
+      else if (['xls', 'xlsx'].includes(ext)) fileType = 'excel';
+      else if (['ppt', 'pptx'].includes(ext)) fileType = 'ppt';
+      return { fileId: index + 1, fileName, fileType, fileSize: '0', fileUrl: url };
+    });
+  }
+
+  const signFileList = ref<FileItem[]>([]);
+  const displayFileList = computed(() => {
+    if (isAddMode.value) return signFileList.value;
+    return convertSignFileToFileItems(formData.value.signFile || '');
+  });
+
+  const handleUploadSuccess = (files: FileItem[]) => {
+    signFileList.value = files;
+    formData.value.signFile = JSON.stringify(files);
+  };
+
+  const handleBack = () => {
+    router.back();
+  };
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return;
+    const valid = await formRef.value.validate().catch(() => false);
+    if (!valid) return;
+    if (!planId.value) {
+      ElMessage.error('缺少关联的区域检查计划ID,无法提交');
+      return;
+    }
+    try {
+
+      const uploadedFileList = await formatAttachmentList(signFileList.value);
+
+      await saveAreaCheckPlanDetailDept({
+          areaPlanId: planId.value,
+          checkedCompanyName: formData.value.inspectedUnit,
+          checkTime: formData.value.checkTime,
+          checkPersonName: formData.value.inspector,
+          // 暂无人员 code 来源,前端先不传或由后端根据名称解析
+          checkAddress: formData.value.checkPlace,
+          overallCheckDesc: formData.value.overallDesc,
+          checkedPersonSign: JSON.stringify(uploadedFileList),
+          areaCheckRecords: checkItems.value.map((item) => ({
+          checkContent: item.checkContent || '',
+          checkStandard: item.checkStandard || '',
+          // 新增检查日志时,检查结果和问题直接使用接口返回的数据
+          checkResult: item.checkResult || '',
+          checkProblem: item.checkProblem || '',
+        })),
+      });
+      ElMessage.success('提交成功');
+      router.back();
+    } catch (e) {
+      console.error('新增检查日志提交失败:', e);
+      ElMessage.error(e?.message || e?.data || '提交失败,请稍后重试');
+    }
+  };
+
+  const loadRecordDetail = async () => {
+    if (recordId.value) {
+      try {
+        const res = await queryAreaCheckRecordDept(recordId.value);
+        const raw = (res as { data?: Record<string, unknown> })?.data ?? res;
+        const r = raw as Record<string, unknown>;
+        formData.value = {
+          inspectedUnit: String(r?.checkedCompanyName ?? r?.checkedCompany ?? ''),
+          inspector: String(r?.checkPersonName ?? r?.checkPerson ?? ''),
+          checkTime: String(r?.checkTime ?? ''),
+          checkPlace: String(r?.checkAddress ?? r?.checkPlace ?? ''),
+          overallDesc: String(r?.overallCheckDesc ?? ''),
+          signFile: JSON.stringify(convertSignFileToFileItems(String(r?.checkedPersonSign ?? ''))),
+        };
+        signFileList.value = JSON.parse(r.checkedPersonSign || '[]');
+        const records = (r?.areaCheckRecords ?? []) as Array<Record<string, unknown>>;
+        checkItems.value = records.map((item) => ({
+          checkContent: (item.checkContent ?? '') as string,
+          checkStandard: (item.checkStandard ?? '') as string,
+          checkResult: (item.checkResult ?? '') as string,
+          checkProblem: (item.checkProblem ?? '') as string,
+        }));
+      } catch (e) {
+        console.error('获取检查记录详情失败:', e);
+      }
+    } else {
+      // 无 recordId 的查看场景,只保留空表单和空检查明细(不依赖路由传递数据)
+      formData.value = {
+        inspectedUnit: '',
+        inspector: '',
+        checkTime: '',
+        checkPlace: '',
+        overallDesc: '',
+        signFile: '',
+      };
+      checkItems.value = [];
+    }
+  };
+
+  const loadTemplateCheckItems = async () => {
+    // 使用部门模板接口获取“新增检查日志”的检查明细
+    if (!planId.value) {
+      // 没有计划ID时,不展示任何检查明细
+      checkItems.value = [];
+      return;
+    }
+    try {
+      const res = await queryAreaCheckRecordTemplateDept(planId.value);
+      const list = ((res as { data?: AreaCheckRecordTemplateItem[] })?.data ?? res) || [];
+      if (Array.isArray(list) && list.length > 0) {
+        checkItems.value = list
+      } else {
+        // 模板为空时,不展示任何检查明细
+        checkItems.value = [];
+      }
+    } catch (e) {
+      console.error('获取检查明细模板失败:', e);
+      // 接口异常时不影响表单填写,但不展示任何假数据
+      checkItems.value = [];
+    }
+  };
+
+  const initFormData = async () => {
+    if (isAddMode.value) {
+      const { planName, checkVenue } = route.query;
+      formData.value = {
+        inspectedUnit: planName ? String(planName) : '',
+        inspector: '',
+        checkTime: '',
+        checkPlace: checkVenue ? String(checkVenue) : '',
+        overallDesc: '',
+        signFile: '',
+      };
+      signFileList.value = [];
+      await loadTemplateCheckItems();
+    } else {
+      await loadRecordDetail();
+    }
+  };
+
+  const previewOnline = (url: string | undefined, type) => {
+    if (url) {
+      previewOnlineRef.value?.open(url, type);
+    }
+  };
+  const viewDetailData = ref<Record<string, unknown>>({});
+  const authority = ref(false)
+  const currentId = computed(() => Number(route.query.planId));
+  const getDetail = async () => {
+    if (!currentId.value) return;
+    try {
+      const res = await queryAreaCheckPlanManageDeptDetail(currentId.value);
+      viewDetailData.value = { ...res };
+      // 如果主责部门责任人ID包含当前登录人ID,则直接回填(被检查单位、 检查人员、检查时间、检查地点),且不可更改
+      let primaryResponsibleDeptPersonCode = res.primaryResponsibleDeptPersonCode?.split(',').map(Number)
+      if(primaryResponsibleDeptPersonCode?.includes(id)){
+        authority.value = true
+        formData.value.inspectedUnit = res.primaryResponsibleDeptName || ''
+        formData.value.inspector = String(res.primaryResponsibleDeptPersonName) || ''
+      }
+    } catch (e) {
+      console.error('获取详情失败:', e);
+    }
+  };
+  onMounted(() => {
+    getDetail()
+    initFormData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+
+  .check-record-form {
+    max-width: 900px;
+    margin-bottom: 24px;
+  }
+
+  .check-items-section {
+    margin-top: 16px;
+    margin-bottom: 24px;
+
+    .section-header {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 12px;
+
+      .section-title {
+        font-weight: 600;
+        font-size: 15px;
+      }
+    }
+  }
+
+  .check-items-table {
+    :deep(.el-textarea__inner) {
+      border: none;
+      box-shadow: none;
+      padding: 4px 0;
+    }
+  }
+
+  .upload-files-disabled {
+    pointer-events: none;
+    opacity: 0.6;
+
+    :deep(.upload-button) {
+      cursor: not-allowed;
+      background-color: #f5f5f5;
+      color: #aaa;
+    }
+
+    :deep(.delete-button) {
+      display: none;
+    }
+  }
+</style>

+ 346 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTaskDept/configs/form.ts

@@ -0,0 +1,346 @@
+import { FormConfig } from '@/types/basic-form';
+
+/** 检查频次选项:每日/每周/每月/每季度/每半年/每年 */
+export const CHECK_FREQUENCY_OPTIONS = [
+  { label: '每日', value: '每日' },
+  { label: '每周', value: '每周' },
+  { label: '每月', value: '每月' },
+  { label: '每季度', value: '每季度' },
+  { label: '每半年', value: '每半年' },
+  { label: '每年', value: '每年' },
+];
+
+/** 检查单模版类别选项(单选):1-安全管理检查单 2-关键业务活动专项检查单 3-日常安全检查单 */
+export const CHECKLIST_CATEGORY_OPTIONS = [
+  { label: '安全管理检查单', value: '安全管理检查单' },
+  { label: '关键业务活动专项检查单', value: '关键业务活动专项检查单' },
+  { label: '日常安全检查单', value: '日常安全检查单' },
+];
+
+// 检查记录入账隐患台账
+export const HIDDEN_DANGER_FORM_CONFIG: FormConfig[] = [
+//   {
+//     prop: 'typeId',
+//     label: '隐患问题类别:',
+//     component: 'ElSelect',
+//     slot: 'typeId',
+//     componentProps: {
+//       placeholder: '请选择隐患问题类别',
+//       style: { width: '100%' },
+//     },
+//     // selectOptions: DANGER_TYPE_OPTIONS,
+//   },
+//   {
+//     prop: 'dangerProblem',
+//     label: '隐患问题:',
+//     component: 'ElInput',
+//     componentProps: {
+//       placeholder: '请输入隐患问题描述',
+//     },
+//   },
+//   {
+//     prop: 'reasonId',
+//     label: '问题主要原因:',
+//     component: 'ElSelect',
+//     componentProps: {
+//       placeholder: '请选择问题主要原因',
+//       style: { width: '100%' },
+//     },
+//     selectOptions: REASON_OPTIONS,
+//   },
+//   {
+//     prop: 'taskSource',
+//     label: '任务来源:',
+//     component: 'ElInput',
+//     componentProps: {
+//       placeholder: '如:上级检查、院内自查',
+//     },
+//   },
+//   {
+//     prop: 'rectificationRequirement',
+//     label: '整改要求:',
+//     component: 'ElInput',
+//     componentProps: {
+//       placeholder: '请输入整改要求',
+//     },
+//   },
+//   {
+//     prop: 'rectificationDepartmentIds',
+//     label: '整改责任部门:',
+//     slot: 'rectificationDepartmentIds',
+//   }, 
+//   {
+//     prop: 'rectificationResponsibleIds',
+//     label: '整改负责人:',
+//     slot: 'rectificationResponsibleIds',
+//   },
+//   {
+//     prop: 'rectificationDeadline',
+//     label: '整改日期:',
+//     component: 'ElDatePicker',
+//     componentProps: {
+//       type: 'date',
+//       placeholder: '请选择整改日期',
+//       valueFormat: 'YYYY-MM-DD',
+//       style: { width: '100%' },
+//     },
+//   },
+//   {
+//     prop: 'reviewDepartmentId',
+//     label: '复查人员所属部门:',
+//     slot: 'reviewDepartmentId',
+//     componentProps: {
+//       placeholder: '请选择复查人员所属部门',
+//       style: { width: '100%' },
+//     },
+//   },
+//   {
+//     prop: 'reviewPersonId',
+//     label: '复查人员:',
+//     slot: 'reviewPerson',
+//     componentProps: {
+//       placeholder: '请选择复查人员',
+//       style: { width: '100%' },
+//     },
+//   },
+//   {
+//     prop: 'isDrawLessonsPush',
+//     label: '举一反三是否推送:',
+//     slot: 'isDrawLessonsPush',
+//   },
+//   {
+//     prop: 'drawLessonsContent',
+//     slot:'drawLessonsContent',
+//     label: '',
+//     component: 'ElInput',
+//     componentProps: {
+//       placeholder: '如:上级检查、院内自查',
+//       style: { paddingLeft: '136.5px' },
+//     },
+//   },
+//   {
+//     prop: 'drawLessonsDepartmentIds',
+//     label: '', //举一反三责任部门:
+//     slot: 'drawLessonsDepartmentIds',
+//   },
+//   {
+//     prop: 'drawLessonsDeadline',
+//     label: '', //举一反三时限:
+//     slot: 'drawLessonsDeadline',
+//   },
+];
+/** 区域检查计划新增/编辑表单配置(与截图字段顺序一致) */
+export const AREA_CHECK_PLAN_FORM_CONFIG: FormConfig[] = [
+  {
+    prop: 'planName',
+    label: '区域检查计划名称:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '输入区域检查计划名称',
+    },
+  },
+  {
+    prop: 'venueCategoryName',
+    label: '检查类别:',
+    slot: 'venueCategoryName',
+  },
+  {
+    prop: 'checkVenue',
+    label: '检查场所:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '输入检查场所',
+    },
+  },
+  {
+    prop: 'primaryResponsibleDeptName',
+    label: '主责部门:',
+    slot: 'primaryResponsibleDept', // 与新增物品领取记录页的部门下拉一致(el-cascader + getAllDepartments)
+    componentProps: {
+      placeholder: '请选择部门',
+    },
+  },
+  {
+    prop: 'selfCheckFrequency',
+    label: '自查频次:',
+    slot: 'selfCheckFrequency',
+  },
+  {
+    prop: 'safetyEmergencyDeptName',
+    label: '安全应急部门:',
+    slot: 'safetyEmergencyDept', // 与新增物品领取记录页的部门下拉一致(el-cascader + getAllDepartments)
+    componentProps: {
+      placeholder: '请选择部门',
+    },
+  },
+  {
+    prop: 'safetyEmergencyCheckFrequency',
+    label: '安全应急部检查频次:',
+    slot: 'safetyEmergencyCheckFrequency',
+  },
+  {
+    prop: 'hospitalLeaderDeptName',
+    label: '院领导部门:',
+    slot: 'hospitalLeaderDept', // 与新增物品领取记录页的部门下拉一致(el-cascader + getAllDepartments)
+    componentProps: {
+      placeholder: '请选择部门',
+    },
+  },
+  {
+    prop: 'hospitalLeaderCheckFrequency',
+    label: '院领导检查频次:',
+    slot: 'hospitalLeaderCheckFrequency',
+  },
+  {
+    prop: 'categoryCode',
+    label: '检查单模版类别:',
+    component: 'ElSelect',
+    componentProps: {
+      placeholder: '选择检查单模版类别,单选',
+      filterable: true,
+      clearable: true,
+      style: { width: '100%' },
+    },
+    selectOptions: [], // 由详情页 queryDictTypeDetail 注入,value=itemCode, label=itemValue
+  },
+  {
+    prop: 'checklistTemplateName',
+    label: '检查单模版名称:',
+    component: 'ElSelect',
+    componentProps: {
+      placeholder: '选择检查单模版名称,单选',
+      filterable: true,
+      clearable: true,
+      style: { width: '100%' },
+    },
+    selectOptions: [], // 由页面根据类别请求模版列表后注入
+  },
+  {
+    prop: 'checkKeyContent',
+    label: '检查重点内容:',
+    component: 'ElInput',
+    componentProps: {
+      type: 'textarea',
+      rows: 4,
+      placeholder: '请输入检查重点内容',
+      maxlength: 300,
+      showWordLimit: true,
+    },
+  },
+];
+
+export const AREA_CHECK_PLAN_FORM_DATA: Record<string, unknown> = {
+  planName: '',
+  venueCategoryName: '',
+  checkVenue: '',
+  /** 主责部门多选:级联绑定 id 数组;提交给后端用 primaryResponsibleDeptName / primaryResponsibleDeptCode(逗号分隔) */
+  primaryResponsibleDeptId: [] as number[],
+  primaryResponsibleDeptName: '',
+  primaryResponsibleDeptCode: '',
+  selfCheckFrequency: '',
+  safetyEmergencyDeptId: null as number | null,
+  safetyEmergencyDeptName: '',
+  safetyEmergencyCheckFrequency: '',
+  hospitalLeaderDeptId: null as number | null,
+  hospitalLeaderDeptName: '',
+  hospitalLeaderCheckFrequency: '',
+  categoryName: '',
+  categoryCode: '',
+  checklistTemplateName: '',
+  checkKeyContent: '',
+};
+
+export const AREA_CHECK_PLAN_FORM_RULES = {
+  planName: [{ required: true, message: '请输入区域检查计划名称', trigger: 'blur' }],
+  venueCategoryName: [{ required: true, message: '请输入检查类别', trigger: 'blur' }],
+  checkVenue: [{ required: true, message: '请输入检查场所', trigger: 'blur' }],
+  primaryResponsibleDeptName: [{ required: true, message: '请选择主责部门', trigger: 'change' }],
+  selfCheckFrequency: [{ required: true, message: '请选择自查频次', trigger: 'change' }],
+  safetyEmergencyCheckFrequency: [{ required: true, message: '请选择安全应急部检查频次', trigger: 'change' }],
+  safetyEmergencyDeptName: [{ required: true, message: '请选择安全应急部门', trigger: 'change' }],
+  hospitalLeaderDeptName: [{ required: true, message: '请选择院领导部门', trigger: 'change' }],
+  hospitalLeaderCheckFrequency: [{ required: true, message: '请选择院领导检查频次', trigger: 'change' }],
+  categoryCode: [{ required: true, message: '请选择检查单模版类别', trigger: 'change' }],
+  checklistTemplateName: [{ required: true, message: '请选择检查单模版名称', trigger: 'change' }],
+  checkKeyContent: [{ required: true, message: '请输入检查重点内容', trigger: 'blur' }],
+};
+/** 问题主要原因:1-人的不安全行为,2-物的不安全状态,3-环境的不利影响,4-管理缺陷 */
+export const REASON_OPTIONS = [
+  { label: '人的不安全行为', value: 1 },
+  { label: '物的不安全状态', value: 2 },
+  { label: '环境的不利影响', value: 3 },
+  { label: '管理缺陷', value: 4 },
+];
+
+
+export const HIDDEN_DANGER_FORM_DATA = {
+  typeId: undefined as number | undefined,
+  dangerProblem: '',
+  reasonId: undefined as number | undefined,
+  taskSource: '', // 任务来源
+  rectificationRequirement: '', // 整改要求
+  rectificationDepartmentIds: '', // 整改责任部门
+  rectificationResponsibleIds: '', // 整改负责人
+  rectificationDeadline: '', //整改日期
+  reviewDepartmentId: undefined as number | undefined, // 复查人员所属部门
+  reviewPersonId: undefined as number | undefined, // 复查人员
+  reviewPersonName: '',
+  isDrawLessonsPush: 0, // 举一反三是否推送
+  drawLessonsContent: '', // 举一反三内容
+  drawLessonsDepartmentIds: '', // 举一反三责任部门
+  drawLessonsDeadline: '', // 举一反三时限
+  // 提交接口需要,下发时再填
+  rectificationResponsiblePerson: '',
+  // 整改详情展示(接口可能返回)
+  rectificationCompletionStatus: '',
+  rectificationCompletionTime: '',
+  attachments: '',
+  reviewComments: '',
+  rectificationResponsibleUserId: '',
+};
+
+
+export const HIDDEN_DANGER_FORM_RULES = {
+  typeId: [{ required: true, message: '请选择隐患问题类别', trigger: 'change' }],
+  dangerProblem: [{ required: true, message: '请输入隐患问题', trigger: 'blur' }],
+  reasonId: [{ required: true, message: '请选择问题主要原因', trigger: 'change' }],
+  taskSource: [{ required: true, message: '请输入任务来源', trigger: 'blur' }],
+  rectificationRequirement: [{ required: true, message: '请输入整改要求', trigger: 'blur' }],
+  rectificationDeadline: [{ required: true, message: '请选择整改日期', trigger: 'change' }],
+  reviewDepartmentId: [{ required: true, message: '请选择复查人员所属部门', trigger: 'change' }],
+  reviewPersonId: [{ required: true, message: '请选择复查人员', trigger: 'change' }],
+  isDrawLessonsPush: [{ required: true, message: '请选择举一反三是否推送', trigger: 'change' }],
+  drawLessonsContent: [
+    {
+      required: true,
+      message: '请输入举一反三内容',
+      trigger: 'blur',
+    },
+  ],
+  drawLessonsDepartmentIds: [
+    {
+      required: true,
+      message: '请选择举一反三责任部门',
+      trigger: 'change',
+    },
+  ],
+  drawLessonsDeadline: [
+    {
+      required: true,
+      message: '请选择举一反三截止日期',
+      trigger: 'change',
+    },
+  ],
+  rectificationCompletionStatus:[
+    { required: true, message: '请输入整改完成情况', trigger: 'blur' }
+  ],
+  rectificationCompletionTime: [
+    { required: true, message: '请选择整改完成时间', trigger: 'change' }
+  ],
+  rectificationDepartmentIds: [
+    { required: true, message: '请选择整改责任部门', trigger: 'change' }
+  ],
+  rectificationResponsibleIds: [
+    { required: true, message: '请选择整改负责人', trigger: 'change' }
+  ],
+};

+ 15 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTaskDept/configs/status.ts

@@ -0,0 +1,15 @@
+// 区域检查计划状态配置(管理员与部门一致):0=未下发 1=进行中 2=已完成 3=已作废
+export const AREA_CHECK_PLAN_STATUS_OPTIONS = [
+  { label: '全部', value: '' as const },
+//   { label: '未下发', value: 0 },
+  { label: '进行中', value: 1 },
+  { label: '已完成', value: 2 },
+  { label: '已作废', value: 3 },
+];
+
+export const AREA_CHECK_PLAN_STATUS_LABEL: Record<string, string> = {
+  '0': '未下发',
+  '1': '进行中',
+  '2': '已完成',
+  '3': '已作废',
+};

+ 113 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTaskDept/configs/tables.ts

@@ -0,0 +1,113 @@
+import type { TableColumnProps } from '@/types/basic-table';
+import { AREA_CHECK_PLAN_STATUS_OPTIONS, AREA_CHECK_PLAN_STATUS_LABEL } from './status';
+
+export { AREA_CHECK_PLAN_STATUS_OPTIONS, AREA_CHECK_PLAN_STATUS_LABEL };
+
+export const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+  maxHeight: 'calc(70vh - 150px)',
+};
+
+// 表格样式与检查单模版管理列表一致(TABLE_OPTIONS、编号/操作列宽)
+export const AREA_CHECK_PLAN_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '编号',
+    type: 'index',
+    align: 'center',
+    width: '80px',
+  },
+  {
+    label: '完成(检查)时间',
+    prop: 'checkTime',
+    align: 'left',
+    minWidth: '180px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '状态',
+    prop: 'checkVenue',
+    align: 'left',
+    minWidth: '120px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '检查人员',
+    prop: 'checkPerson',
+    align: 'left',
+    minWidth: '120px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '检查场所类别',
+    prop: 'checkPlaceCategory',
+    align: 'left',
+    minWidth: '160px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '检查场所',
+    prop: 'checkPlace',
+    align: 'left',
+    minWidth: '120px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '检查频次',
+    prop: 'planName',
+    align: 'left',
+    minWidth: '120px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '检查重点内容',
+    prop: 'primaryResponsibleDeptName',
+    slot: 'primaryResponsibleDeptName',
+    align: 'left',
+    width: '200px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '整体检查情况描述',
+    prop: 'overallCheckDesc',
+    align: 'left',
+    minWidth: '200px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '被检查人签字',
+    prop: 'sign',
+    align: 'left',
+    minWidth: '140px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '检查项总数',
+    prop: 'checkItemTotal',
+    align: 'left',
+    minWidth: '160px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '合格项数',
+    prop: 'qualifiedItemNum',
+    align: 'left',
+    minWidth: '160px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '不合格项数',
+    prop: 'unqualifiedItemNum',
+    align: 'left',
+    minWidth: '160px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '操作',
+    prop: 'action',
+    slot: 'action',
+    fixed: 'right',
+    width: '150px',
+    align: 'left',
+  },
+];

+ 51 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTaskDept/configs/types.ts

@@ -0,0 +1,51 @@
+/** 区域检查计划状态:0=未开始 1=进行中 2=已完成 3=已终止 */
+export type AreaCheckPlanStatus = 0 | 1 | 2 | 3;
+
+export interface AreaCheckPlanRecord {
+  id?: number;
+  checkVenue?: string;
+  status?: AreaCheckPlanStatus;
+  venueCategoryName?: string;
+  planName?: string;
+  primaryResponsibleDeptName?: string;
+  primaryResponsibleDeptCode?: string;
+  selfCheckFrequency?: string;
+  mainDeptExecutorGroupName?: string;
+  mainDeptExecGroupCode?: string;
+  mainDeptResponsiblePerson?: string;
+  mainDeptResponsiblePersonCode?: string;
+  safetyEmergencyDeptName?: string;
+  safetyEmergencyDeptCode?: string;
+  safetyEmergencyCheckFrequency?: string;
+  safetyEmergencyExecutorGroupName?: string;
+  safetyEmergencyExecGroupCode?: string;
+  safetyEmergencyResponsiblePerson?: string;
+  safetyEmergencyPersonCode?: string;
+  hospitalLeaderDeptName?: string;
+  hospitalLeaderDeptCode?: string;
+  hospitalLeaderCheckFrequency?: string;
+  hospitalLeaderExecutorGroupName?: string;
+  hospitalLeaderExecGroupCode?: string;
+  hospitalLeaderResponsiblePerson?: string;
+  hospitalLeaderPersonCode?: string;
+  checkKeyContent?: string;
+  categoryName?: string;
+  categoryCode?: string;
+  checklistTemplateName?: string;
+  needOverallDesc?: boolean;
+  needInspectedSign?: boolean;
+  planStartTime?: string;
+  planEndTime?: string;
+  isDeleted?: number | string;
+  createdAt?: string;
+  updatedAt?: string;
+  [key: string]: unknown;
+}
+
+export interface AreaCheckPlanQuery {
+  keyword?: string;
+  status?: AreaCheckPlanStatus | '';
+  venueCategory?: string;
+  planStartTime?: string;
+  planEndTime?: string;
+}