hejianhao
2026-01-23 bfe0c1e69dbc54b4c6c7204c32db7a20cbb733ab
Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/smartBuilding
1个文件已删除
3个文件已添加
1个文件已修改
1131 ■■■■■ 已修改文件
screen/src/view/hiddenDanger/README.md 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/hiddenDanger/api.txt 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/hiddenDanger/index.vue 782 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/hiddenDanger/service.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/planOfAudit/api.txt 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/hiddenDanger/README.md
New file
@@ -0,0 +1,231 @@
# 隐患列表页面功能说明
## 📋 页面概述
隐患列表页面用于展示和管理项目中的各类隐患信息,支持多种筛选条件和详情查看。
## 🎯 核心功能
### 1. 路由参数支持
页面支持从路由获取以下参数:
- **activeTab**: 隐患类型选中的 tab
  - `1` = 重大隐患
  - `2` = 安全隐患
  - `3` = 设备隐患
- **projectId**: 项目ID
  - 如果有此参数,代表这是项目端使用
  - 项目端模式下,搜索区域不展示项目下拉框
- **projectName**: 项目名称(用于页面标题显示)
### 2. Tab 切换
三个隐患类型 Tab:
- **重大隐患**: 显示隐患类型筛选(安全/设备)
- **安全隐患**: 显示隐患等级筛选(一级/二级/三级/四级)
- **设备隐患**: 显示隐患等级筛选(一级/二级/三级/四级)
切换 Tab 时会自动重置筛选条件并重新加载数据。
### 3. 搜索筛选
支持以下筛选条件:
| 筛选项 | 说明 | 显示条件 |
|--------|------|----------|
| 项目选择 | 选择要查看的项目 | 仅非项目端显示 |
| 整改状态 | 未整改/部分整改/已整改 | 始终显示 |
| 隐患类型 | 安全隐患/设备隐患 | 仅重大隐患 Tab 显示 |
| 隐患等级 | 一级/二级/三级/四级 | 安全和设备隐患 Tab 显示 |
| 开始时间 | 检查时间范围-开始 | 始终显示 |
| 结束时间 | 检查时间范围-结束 | 始终显示 |
| 搜索内容 | 关键词搜索 | 始终显示 |
### 4. 表格展示
表格列说明:
| 列名 | 字段 | 说明 |
|------|------|------|
| 项目名称 | projectName | 仅非项目端显示 |
| 检查计划 | planName | - |
| 检查内容 | hierarchyName | - |
| 问题描述 | description | - |
| 问题部位 | problemPart | - |
| 问题备注 | remark | - |
| 隐患等级 | level | 0=一级, 1=二级, 2=三级, 3=四级 |
| 隐患类型 | type | 仅重大隐患 Tab 显示,1=安全, 2=设备 |
| 图片 | fileNum | 点击可预览图片 |
| 检查时间 | createTime | - |
| 整改状态 | reformed | true=已整改(绿色), false=未整改(红色) |
| 责任人 | responsiblePerson | - |
| 操作 | - | 编辑责任人、详情 |
### 5. 编辑责任人
- 点击"编辑责任人"按钮打开弹窗
- 输入新的责任人姓名
- 保存后更新表格数据和后端
### 6. 查看详情
详情弹窗包含两个 Tab:
#### 基本信息
- 计划名称、项目名称
- 检查内容、责任人
- 问题部位、创建时间
- 问题描述
- 规范条款
- 建议措施
- 隐患等级、整改状态
- 扣除积分、附加分数
- 备注
- 问题图片(可点击预览)
#### 复查记录
- 显示所有复查记录列表
- 每条记录包含:
  - 复查内容、复查状态
  - 复查图片、复核人
  - 创建时间、更新时间
- 如果没有复查记录,显示"暂无复查记录"
### 7. 图片预览
- 点击表格或详情中的图片数量可预览图片
- 支持多图浏览
- 如果没有图片,按钮显示为灰色不可点击
### 8. 分页
- 支持每页显示 10/20/50/100 条
- 支持页码切换
- 显示总数据量
## 🔧 技术实现
### API 接口
| 接口 | 方法 | 说明 |
|------|------|------|
| getLoginNameVoList | GET | 获取项目列表 |
| getHiddenDangerList | POST | 获取隐患列表(分页) |
| getHiddenDangerDetail | GET | 获取隐患详情 |
| editHiddenDangerResponsiblePerson | POST | 编辑责任人 |
### 枚举配置
使用 `riskSourceMap.js` 中的枚举:
```javascript
// 隐患等级
errorLevelMap = {
  P1: '一级隐患',
  P2: '二级隐患',
  P3: '三级隐患',
  P4: '四级隐患'
}
// 整改状态
errorReviewStateMap = {
  NOT_REFORMED: '未整改',
  PARTIAL_REFORMED: '部分整改',
  REFORMED: '已整改'
}
```
### 数据流转
1. **页面初始化**
   - 从路由获取参数(activeTab, projectId, projectName)
   - 初始化下拉选项
   - 加载项目列表(非项目端)
   - 加载表格数据
2. **Tab 切换**
   - 重置搜索条件
   - 根据 Tab 设置对应的 type 参数
   - 重新加载数据
3. **搜索筛选**
   - 收集所有筛选条件
   - 重置页码为第一页
   - 调用接口加载数据
4. **编辑责任人**
   - 打开弹窗,回显当前责任人
   - 输入新责任人
   - 调用接口保存
   - 更新表格数据
5. **查看详情**
   - 根据 problemId 获取详情数据
   - 展示基本信息和复查记录
   - 支持图片预览
## 🎨 UI 特性
- **整改状态动画**: 已整改显示绿色波纹动画,未整改显示红色波纹动画
- **响应式布局**: 表格支持横向滚动,列宽自适应
- **悬浮提示**: 长文本支持悬浮显示完整内容
- **加载状态**: 数据加载时显示 loading 效果
- **空状态处理**: 无数据时友好提示
## 📝 使用示例
### 从其他页面跳转
```javascript
// 跳转到安全隐患列表
this.$router.push({
  path: '/hiddenDanger',
  query: {
    activeTab: 2,
    projectId: '123',
    projectName: '某某项目'
  }
})
```
### 项目端使用
```javascript
// 项目端使用(不显示项目选择)
this.$router.push({
  path: '/hiddenDanger',
  query: {
    projectId: '123',
    projectName: '某某项目'
  }
})
```
### 平台端使用
```javascript
// 平台端使用(显示项目选择)
this.$router.push({
  path: '/hiddenDanger'
})
```
## 🐛 注意事项
1. **日期格式**: 后端接口接收的日期格式为 `YYYY-MM-DD HH:mm:ss`
2. **整改状态**: 接口参数 reformed 为数字(1=已整改, 0=未整改),但响应数据为布尔值
3. **隐患等级**: level 字段为数字索引(0-3),需要映射为文本显示
4. **图片预览**: 需要确保 userFiles 数组中包含图片 URL
5. **复查记录**: 详情接口返回的数据结构需要包含 reviewRecords 数组
## 🔄 后续优化建议
1. 添加导出功能(导出 Excel)
2. 添加批量操作(批量分配责任人)
3. 添加隐患统计图表
4. 支持隐患状态流转(提交整改、审核等)
5. 添加消息通知功能
screen/src/view/hiddenDanger/api.txt
New file
@@ -0,0 +1,48 @@
1、getLoginNameVoList 是get params传参
       请求参数:userId
       响应:name 名称; id
2、getHiddenDangerList 是post json传参
       请求参数:
                content    内容
                endTime    结束时间
                pageNum    分页参数,当前页码
                pageSize    分页参数,每页数量,默认为10
                projectId    项目id
                reformed    整改情况 1已整改 0未整改
                startTime    开始时间
                type    隐患类型 1=安全 2=设备
        响应参数:
               createTime    检查时间    string
                description    问题描述    string
                fileNum    图片数量    integer
                hierarchyName    检查内容    string
                id        integer
                level    问题等级 0=一级 1=二级....    integer
                planName    计划名称    string
                problemId    问题id (用于查详情)    integer
                problemPart    问题部位    string
                reformed    整改情况 true已整改 false未整改    boolean
                remark    问题备注    string
                responsiblePerson    责任人    string
                type    问题类型 1=安全 2=设备    integer
                userFiles    图片信息    array    UserFile
                        contentType        string
                        createTime        string
                        creatorId        integer
                        deleted        boolean
                        extension        string
                        id        integer
                        name        string
                        originFileName        string
                        size        integer
                        updateTime        string
3、getHiddenDangerDetail get 路径传参
      请求参数:
                problemId    问题id
4、  editHiddenDangerResponsiblePerson    post json传参
      请求参数:responsiblePerson    责任人
               id
screen/src/view/hiddenDanger/index.vue
@@ -29,21 +29,23 @@
      </div>
      <!-- 搜索 -->
      <div class="flex a-center gap--16 mt--24 mb--20">
        <!-- 项目选择(非项目端才显示) -->
        <div
          v-if="!projectId"
          v-if="!isProjectMode"
          class="flex a-center j-between pl--18 pr--15 h--40 relative w--300 bgcolor1 searchBox"
        >
          <el-select
            filterable
            v-model="projectId"
            v-model="searchForm.projectId"
            placeholder="请选择项目"
            class="flex1"
            clearable
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
              v-for="item in projectList"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            >
            </el-option>
          </el-select>
@@ -52,20 +54,22 @@
            class="w--15 h--16 shrink0"
          />
        </div>
        <!-- 整改状态 -->
        <div
          class="flex a-center j-between pl--18 pr--15 h--40 relative w--300 bgcolor1 searchBox"
        >
          <el-select
            filterable
            v-model="projectId"
            v-model="searchForm.reformed"
            placeholder="请选择整改状态"
            class="flex1"
            clearable
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
              v-for="item in reformedOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            >
            </el-option>
          </el-select>
@@ -74,44 +78,43 @@
            class="w--15 h--16 shrink0"
          />
        </div>
        <!-- 隐患类型(仅重大隐患显示) -->
        <div
          v-if="activeTab == 1"
          v-if="showTypeFilter"
          class="flex a-center j-between pl--18 pr--15 h--40 relative w--300 bgcolor1 searchBox"
        >
          <el-select
            filterable
            v-model="projectId"
            v-model="searchForm.type"
            placeholder="请选择隐患类型"
            class="flex1"
            clearable
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
            >
            </el-option>
            <el-option label="安全隐患" :value="1"></el-option>
            <el-option label="设备隐患" :value="2"></el-option>
          </el-select>
          <img
            src="../../assets/img/down-circle@2x.png"
            class="w--15 h--16 shrink0"
          />
        </div>
        <!-- 隐患等级(安全和设备隐患显示) -->
        <div
          v-else
          v-if="showLevelFilter"
          class="flex a-center j-between pl--18 pr--15 h--40 relative w--300 bgcolor1 searchBox"
        >
          <el-select
            filterable
            v-model="projectId"
            v-model="searchForm.level"
            placeholder="请选择隐患等级"
            class="flex1"
            clearable
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
              v-for="item in levelOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            >
            </el-option>
          </el-select>
@@ -120,12 +123,14 @@
            class="w--15 h--16 shrink0"
          />
        </div>
        <!-- 开始时间 -->
        <div
          @click="handleClick(1)"
          class="flex a-center j-between px--20 w--300 h--40 relative bgcolor1 picker"
        >
          <el-date-picker
            v-model="dateTime"
            v-model="startDateTime"
            ref="datetime1"
            type="datetime"
            placeholder=""
@@ -134,27 +139,29 @@
          <div
            class="absolute w100 h100 flex a-center c-fff fs--14 SHS"
            style="width: calc(100% - 20px)"
            v-if="dateTxt"
            v-if="startDateTxt"
          >
            {{ dateTxt }}
            {{ startDateTxt }}
          </div>
          <div
            v-else
            class="absolute w100 h100 flex a-center c-fff fs--14 SHSR"
          >
            请选择日期
            开始时间
          </div>
          <img
            src="../../assets/img/home/Shape@2x (6).png"
            class="w--13 h--13 shrink0"
          />
        </div>
        <!-- 结束时间 -->
        <div
          @click="handleClick(2)"
          class="flex a-center j-between px--20 w--300 h--40 relative bgcolor1 picker"
        >
          <el-date-picker
            v-model="dateTime"
            v-model="endDateTime"
            ref="datetime2"
            type="datetime"
            placeholder=""
@@ -163,118 +170,165 @@
          <div
            class="absolute w100 h100 flex a-center c-fff fs--14 SHS"
            style="width: calc(100% - 20px)"
            v-if="dateTxt"
            v-if="endDateTxt"
          >
            {{ dateTxt }}
            {{ endDateTxt }}
          </div>
          <div
            v-else
            class="absolute w100 h100 flex a-center c-fff fs--14 SHSR"
          >
            请选择日期
            结束时间
          </div>
          <img
            src="../../assets/img/home/Shape@2x (6).png"
            class="w--13 h--13 shrink0"
          />
        </div>
        <!-- 搜索内容 -->
        <div
          class="flex a-center j-between w--300 pl--20 h--40 relative bgcolor1 searchBox"
        >
          <el-select
            filterable
            v-model="projectId"
          <el-input
            v-model="searchForm.content"
            placeholder="请输入搜索内容"
            class="flex1"
            clearable
            @keyup.enter.native="handleSearch"
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
            >
            </el-option>
          </el-select>
          <div class="btn SHSR">搜索</div>
          </el-input>
          <div class="btn SHSR" @click="handleSearch">搜索</div>
        </div>
      </div>
      <!-- 表格 -->
      <CustomTable
        :data="[{ projectName: '项目777' }, { projectName: '项目2' }]"
        :data="tableData"
        height="800"
        style="border: 2px solid #3d7fff"
        v-loading="loading"
      >
        <el-table-column
          v-if="!projectId"
          v-if="!isProjectMode"
          label="项目名称"
          prop="projectName"
          min-width="150"
        />
        <el-table-column label="检查计划" prop="projectName" />
        <el-table-column label="检查内容" prop="projectName" />
        <el-table-column label="问题描述" prop="projectName" />
        <el-table-column label="问题部位" prop="projectName" />
        <el-table-column label="问题备注" prop="projectName" />
        <el-table-column label="隐患等级" prop="projectName" />
        <el-table-column
          label="检查计划"
          prop="planName"
          min-width="150"
          show-overflow-tooltip
        />
        <el-table-column
          label="检查内容"
          prop="hierarchyName"
          min-width="150"
          show-overflow-tooltip
        />
        <el-table-column
          label="问题描述"
          prop="description"
          min-width="200"
          show-overflow-tooltip
        />
        <el-table-column
          label="问题部位"
          prop="problemPart"
          min-width="150"
          show-overflow-tooltip
        />
        <el-table-column
          label="问题备注"
          prop="remark"
          min-width="150"
          show-overflow-tooltip
        />
        <el-table-column
          label="隐患等级"
          min-width="120"
        >
          <template #default="{ row }">
            <span>{{ getLevelText(row.level) }}</span>
          </template>
        </el-table-column>
        <el-table-column
          v-if="activeTab == 1"
          label="隐患类型"
          prop="projectName"
        />
        <el-table-column label="图片" prop="projectName">
          <template #default="{ row }"
            ><div
          min-width="120"
        >
          <template #default="{ row }">
            <span>{{ row.type === 1 ? '安全隐患' : row.type === 2 ? '设备隐患' : '-' }}</span>
          </template>
        </el-table-column>
        <el-table-column label="图片" min-width="100">
          <template #default="{ row }">
            <div
              class="btnColor cursor-pointer"
              :class="false && 'disableColor'"
              @click="previewVisible = true"
              :class="!row.userFiles || row.userFiles.length === 0 ? 'disableColor' : ''"
              @click="handlePreviewImages(row.userFiles)"
            >
              1张
              {{ row.fileNum || 0 }}张
            </div>
          </template>
        </el-table-column>
        <el-table-column label="检查时间" prop="projectName" />
        <el-table-column label="整改状态" prop="projectName">
          <template #default="{ row, $index }">
        <el-table-column
          label="检查时间"
          prop="createTime"
          min-width="180"
        />
        <el-table-column label="整改状态" min-width="120">
          <template #default="{ row }">
            <div class="flex a-center j-center">
              <div class="ripple">
                <div
                  class="ripple-wave wave-1"
                  :class="{ bgColor33: $index === 0 }"
                  :class="{ bgColor33: row.reformed }"
                ></div>
                <div
                  class="ripple-wave wave-2"
                  :class="{ bgColor44: $index === 0 }"
                  :class="{ bgColor44: row.reformed }"
                ></div>
                <div
                  class="ripple-wave wave-3"
                  :class="{ bgColor55: $index === 0 }"
                  :class="{ bgColor55: row.reformed }"
                ></div>
              </div>
              <div class="ml--6 SHSR">{{ $index }}</div>
              <div class="ml--6 SHSR">{{ getReformedText(row.reformed) }}</div>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="责任人" prop="projectName" />
        <el-table-column label="操作">
        <el-table-column
          label="责任人"
          prop="responsiblePerson"
          min-width="120"
        />
        <el-table-column label="操作" min-width="200" fixed="right">
          <template #default="{ row }">
            <el-button @click="updatePeople = true" type="text" class="btnColor"
              >编辑责任人</el-button
            <el-button
              @click="handleEditPeople(row)"
              type="text"
              class="btnColor"
            >
              编辑责任人
            </el-button>
            <el-button
              type="text"
              class="btnColor"
              @click="detailVisible = true"
              >详情</el-button
              @click="handleViewDetail(row)"
            >
              详情
            </el-button>
          </template>
        </el-table-column>
      </CustomTable>
      <CustomPagination
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="currentPage"
        :page-sizes="[100, 200, 300, 400]"
        :page-size="10"
        :total="400"
        :current-page="pageNum"
        :page-sizes="[10, 20, 50, 100]"
        :page-size="pageSize"
        :total="total"
      />
    </div>
    <!-- 修改责任人 -->
@@ -302,7 +356,11 @@
      </div>
    </CustomDialog>
    <!-- 隐患详情 -->
    <CustomDialog title="项目名称" :visible.sync="detailVisible" width="1420px">
    <CustomDialog
      :title="detailData ? detailData.projectName || '隐患详情' : '隐患详情'"
      :visible.sync="detailVisible"
      width="1420px"
    >
      <el-tabs v-model="activeName" type="card" @tab-click="handleClickTab">
        <el-tab-pane label="基本信息" name="first">
          <div class="oa" style="height: 700px">
@@ -310,13 +368,13 @@
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">计划名称:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                  {{ currentRow && currentRow.planName || '-' }}
                </div>
              </div>
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">项目名称:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                  {{ currentRow && currentRow.projectName || '-' }}
                </div>
              </div>
            </div>
@@ -324,117 +382,142 @@
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">检查内容:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                  {{ currentRow && currentRow.hierarchyName || '-' }}
                </div>
              </div>
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">责任人:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                  {{ currentRow && currentRow.responsiblePerson || '-' }}
                </div>
              </div>
            </div>
            <div class="flex j-between gap--30 mt--30">
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">问题部门:</div>
                <div class="SHSR fs--16 lh--16 mb--17 color1">问题部位:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                  {{ currentRow && currentRow.problemPart || '-' }}
                </div>
              </div>
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">创建时间:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                  {{ currentRow && currentRow.createTime || '-' }}
                </div>
              </div>
            </div>
            <div class="mt--30">
              <div class="SHSR fs--16 lh--16 mb--17 color1">规范条款:</div>
              <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                计划名称计划名称计划名称
              <div class="min-h--60 py--20 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                <template v-if="detailData && detailData.specificTerms && detailData.specificTerms.length > 0">
                  <div v-for="(term, index) in detailData.specificTerms" :key="'term-' + index" class="mb--10">
                    {{ index + 1 }}. {{ term }}
                  </div>
                </template>
                <template v-else>
                  {{ detailData && detailData.specificTermsStr || '-' }}
                </template>
              </div>
            </div>
            <div class="mt--30">
              <div class="SHSR fs--16 lh--16 mb--17 color1">建议措施:</div>
              <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                计划名称计划名称计划名称
              <div class="min-h--60 py--20 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                <template v-if="detailData && detailData.suggestActions && detailData.suggestActions.length > 0">
                  <div v-for="(action, index) in detailData.suggestActions" :key="'action-' + index" class="mb--10">
                    {{ index + 1 }}. {{ action }}
                  </div>
                </template>
                <template v-else>
                  {{ detailData && detailData.suggestActionsStr || '-' }}
                </template>
              </div>
            </div>
            <div class="flex j-between gap--30 mt--30">
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">问题数量:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                  {{ getNumberFactorName() }}
                </div>
              </div>
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">扣除积分:</div>
                <div class="SHSR fs--16 lh--16 mb--17 color1">扣除分数:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                  {{ getNumberFactorDeductPoint() }}
                </div>
              </div>
            </div>
            <div class="flex j-between gap--30 mt--30">
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">附加分数:</div>
                <div class="SHSR fs--16 lh--16 mb--17 color1">附加加分:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                  {{ getNumberFactorAdditionPoint() }}
                </div>
              </div>
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">备注:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                </div>
                <!-- 占位 -->
              </div>
            </div>
            <div class="mt--30">
              <div class="SHSR fs--16 lh--16 mb--17 color1">备注:</div>
              <div class="min-h--60 py--20 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                {{ detailData && detailData.remark || '-' }}
              </div>
            </div>
          </div>
        </el-tab-pane>
        <el-tab-pane label="复查记录(无)" name="second">
        <el-tab-pane
          :label="getReviewRecordsLabel()"
          name="second"
        >
          <div class="oa" style="height: 700px">
            <div class="flex j-between gap--30 mt--30">
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">复合内容:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                </div>
              </div>
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">复合状态:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                </div>
              </div>
            <div v-if="!detailData || !detailData.errorReview" class="txt-center c-fff fs--16 mt--50">
              暂无复查记录
            </div>
            <div class="flex j-between gap--30 mt--30">
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">复合图片:</div>
                <div
                  class="lh--60 px--19 btnColor SHSBold fs--18 w100 cursor-pointer"
                  :class="false && 'disableColor'"
                  @click="previewVisible = true"
                >
                  1张
            <div v-else>
              <div class="flex j-between gap--30 mt--30">
                <div class="flex1">
                  <div class="SHSR fs--16 lh--16 mb--17 color1">复查内容:</div>
                  <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                    {{ detailData.errorReview.content || '-' }}
                  </div>
                </div>
                <div class="flex1">
                  <div class="SHSR fs--16 lh--16 mb--17 color1">复查状态:</div>
                  <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                    {{ getReviewStateText(detailData.errorReview.state) }}
                  </div>
                </div>
              </div>
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">复核人:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
              <div class="flex j-between gap--30 mt--30">
                <div class="flex1">
                  <div class="SHSR fs--16 lh--16 mb--17 color1">复查图片:</div>
                  <div
                    class="lh--60 px--19 btnColor SHSBold fs--18 w100 cursor-pointer"
                    :class="!detailData.errorReview.img || detailData.errorReview.img.length === 0 ? 'disableColor' : ''"
                    @click="handlePreviewReviewImages(detailData.errorReview.img)"
                  >
                    {{ detailData.errorReview.img && detailData.errorReview.img.length || 0 }}张
                  </div>
                </div>
                <div class="flex1">
                  <div class="SHSR fs--16 lh--16 mb--17 color1">复核人:</div>
                  <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                    {{ detailData.errorReview.creator || '-' }}
                  </div>
                </div>
              </div>
            </div>
            <div class="flex j-between gap--30 mt--30">
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">创建时间:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
              <div class="flex j-between gap--30 mt--30">
                <div class="flex1">
                  <div class="SHSR fs--16 lh--16 mb--17 color1">创建时间:</div>
                  <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                    {{ detailData.errorReview.createTime || '-' }}
                  </div>
                </div>
              </div>
              <div class="flex1">
                <div class="SHSR fs--16 lh--16 mb--17 color1">更新时间:</div>
                <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                  计划名称计划名称计划名称
                <div class="flex1">
                  <div class="SHSR fs--16 lh--16 mb--17 color1">更新时间:</div>
                  <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor6">
                    {{ detailData.errorReview.updateTime || '-' }}
                  </div>
                </div>
              </div>
            </div>
@@ -445,7 +528,7 @@
    <!-- 图片预览 -->
    <CustomPreview
      :show="previewVisible"
      :imgArr="[]"
      :imgArr="previewImages"
      @close="previewVisible = false"
    />
  </div>
@@ -454,56 +537,287 @@
<script>
import HeaderBox from "../header/index.vue";
import moment from "moment";
import {
  getLoginNameVoList,
  getHiddenDangerList,
  getHiddenDangerDetail,
  editHiddenDangerResponsiblePerson,
} from "./service";
import {
  errorLevelMap,
  errorReviewStateMap,
} from "@/utils/riskSourceMap";
export default {
  components: { HeaderBox },
  props: {},
  data() {
    return {
      activeTab: 1,
      dateTime: null,
      projectId: null,
      projectName: "",
      dateTxt: "",
      currentPage: 1,
      // Tab 相关
      activeTab: 1, // 1=重大隐患 2=安全隐患 3=设备隐患
      // 路由参数
      projectId: null, // 项目ID,如果有则是项目端使用
      projectName: "", // 项目名称
      // 搜索条件
      searchForm: {
        projectId: null, // 选中的项目ID
        reformed: null, // 整改状态:1已整改 0未整改
        type: null, // 隐患类型:1=安全 2=设备
        level: null, // 隐患等级:0=一级 1=二级 2=三级 3=四级
        startTime: null, // 开始时间
        endTime: null, // 结束时间
        content: "", // 搜索内容
      },
      // 日期选择
      startDateTime: null,
      endDateTime: null,
      startDateTxt: "",
      endDateTxt: "",
      // 下拉选项
      projectList: [], // 项目列表
      reformedOptions: [], // 整改状态选项
      levelOptions: [], // 隐患等级选项
      // 表格数据
      tableData: [],
      total: 0,
      pageNum: 1,
      pageSize: 10,
      loading: false,
      // 弹窗相关
      updatePeople: false,
      peopleName: "",
      currentEditRow: null, // 当前编辑的行数据
      detailVisible: false,
      detailData: null, // 详情数据
      currentRow: null, // 当前查看详情的列表行数据
      activeName: "first",
      previewVisible: false,
      previewImages: [], // 预览图片列表
    };
  },
  computed: {},
  computed: {
    // 是否是项目端(有projectId参数)
    isProjectMode() {
      return !!this.projectId;
    },
    // 当前隐患类型(根据activeTab判断)
    currentType() {
      // 1=重大隐患(暂无对应type) 2=安全隐患(type=1) 3=设备隐患(type=2)
      if (this.activeTab === 2) return 1;
      if (this.activeTab === 3) return 2;
      return null;
    },
    // 是否显示隐患类型筛选(仅重大隐患显示)
    showTypeFilter() {
      return this.activeTab === 1;
    },
    // 是否显示隐患等级筛选(安全和设备隐患显示)
    showLevelFilter() {
      return this.activeTab !== 1;
    },
  },
  watch: {
    dateTime: {
      handler(newVal, oldVal) {
    // 监听 tab 切换
    activeTab: {
      handler(newVal) {
        // 切换 tab 时重置筛选条件并重新加载数据
        this.resetSearchForm();
        this.pageNum = 1;
        this.loadTableData();
      },
    },
    // 监听开始时间
    startDateTime: {
      handler(newVal) {
        if (newVal) {
          this.dateTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
          this.startDateTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
          this.searchForm.startTime = moment(newVal).format("YYYY-MM-DD HH:mm:ss");
        } else {
          this.startDateTxt = "";
          this.searchForm.startTime = null;
        }
      },
      immediate: true,
    },
    // 监听结束时间
    endDateTime: {
      handler(newVal) {
        if (newVal) {
          this.endDateTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
          this.searchForm.endTime = moment(newVal).format("YYYY-MM-DD HH:mm:ss");
        } else {
          this.endDateTxt = "";
          this.searchForm.endTime = null;
        }
      },
    },
  },
  created() {
    // 从路由获取参数
    if (this.$route.query.activeTab) {
      this.activeTab = this.$route.query.activeTab;
      this.activeTab = Number(this.$route.query.activeTab);
    }
    if (this.$route.query.projectId) {
      this.projectId = this.$route.query.projectId;
      this.searchForm.projectId = this.$route.query.projectId;
    }
    if (this.$route.query.projectName) {
      this.projectName = this.$route.query.projectName;
    }
    // 初始化下拉选项
    this.initOptions();
    // 加载项目列表(如果不是项目端)
    if (!this.isProjectMode) {
      this.loadProjectList();
    }
    // 加载表格数据
    this.loadTableData();
  },
  mounted() {},
  methods: {
    handleClickTab(tab) {
      this.activeName = tab.name;
    // 初始化下拉选项
    initOptions() {
      // 整改状态选项
      this.reformedOptions = [
        { label: '未整改', value: 0 },
        { label: '已整改', value: 1 }
      ];
      // 隐患等级选项
      this.levelOptions = [
        { label: '一级隐患', value: 0 },
        { label: '二级隐患', value: 1 },
        { label: '三级隐患', value: 2 },
        { label: '四级隐患', value: 3 }
      ];
    },
    // 重置搜索表单
    resetSearchForm() {
      this.searchForm = {
        projectId: this.projectId || null,
        reformed: null,
        type: this.currentType,
        level: null,
        startTime: null,
        endTime: null,
        content: "",
      };
      this.startDateTime = null;
      this.endDateTime = null;
    },
    // 加载项目列表
    async loadProjectList() {
      try {
        const res = await getLoginNameVoList({});
        if (res && res) {
          this.projectList = res.map((item) => ({
            label: item.name,
            value: item.id,
          }));
        }
      } catch (error) {
        console.error("加载项目列表失败:", error);
      }
    },
    // 加载表格数据
    async loadTableData() {
      this.loading = true;
      try {
        const params = {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
          ...this.searchForm,
          type: this.currentType,
        };
        // 过滤掉空值
        Object.keys(params).forEach((key) => {
          if (params[key] === null || params[key] === "") {
            delete params[key];
          }
        });
        const res = await getHiddenDangerList(params);
        if (res ) {
          this.tableData = res.records || [];
          this.total = res.total || 0;
        }
      } catch (error) {
        console.error("加载表格数据失败:", error);
        this.$message.error("加载数据失败");
      } finally {
        this.loading = false;
      }
    },
    // 搜索
    handleSearch() {
      this.pageNum = 1;
      this.loadTableData();
    },
    // 点击日期选择器
    handleClick(type) {
      if (type === 1) {
        this.$refs.datetime1.focus();
      } else {
        this.$refs.datetime2.focus();
      }
    },
    // 获取隐患等级文本
    getLevelText(level) {
      const key = `P${level + 1}`;
      return errorLevelMap[key] || "-";
    },
    // 获取整改状态文本
    getReformedText(reformed) {
      return reformed ? "已整改" : "未整改";
    },
    // 预览图片
    handlePreviewImages(userFiles) {
      if (!userFiles || userFiles.length === 0) {
        this.$message.warning("暂无图片");
        return;
      }
      this.previewImages = userFiles.map((file) => file.url || file.name);
      this.previewVisible = true;
    },
    // 编辑责任人
    handleEditPeople(row) {
      this.currentEditRow = row;
      this.peopleName = row.responsiblePerson || "";
      this.updatePeople = true;
    },
    // 关闭编辑责任人弹窗
    closeUpdatePeople() {
      this.updatePeople = false;
      this.peopleName = "";
      this.currentEditRow = null;
    },
    saveUpdatePeople() {
    // 保存责任人
    async saveUpdatePeople() {
      if (!this.peopleName) {
        this.$message({
          message: "请输入责任人",
@@ -511,23 +825,153 @@
        });
        return;
      }
      this.$message({
        message: "保存成功",
        type: "success",
      });
      this.closeUpdatePeople();
      try {
        await editHiddenDangerResponsiblePerson({
          id: this.currentEditRow.id,
          responsiblePerson: this.peopleName,
        });
        this.$message({
          message: "保存成功",
          type: "success",
        });
        // 更新表格数据
        this.currentEditRow.responsiblePerson = this.peopleName;
        this.closeUpdatePeople();
      } catch (error) {
        console.error("保存责任人失败:", error);
        this.$message.error("保存失败");
      }
    },
    // 查看详情
    async handleViewDetail(row) {
      try {
        // 保存当前行数据
        this.currentRow = row;
        const res = await getHiddenDangerDetail(row.problemId);
        if (res && res) {
          this.detailData = res;
          this.detailVisible = true;
          this.activeName = "first";
        }
      } catch (error) {
        console.error("加载详情失败:", error);
        this.$message.error("加载详情失败");
      }
    },
    // 切换详情 tab
    handleClickTab(tab) {
      this.activeName = tab.name;
    },
    // 获取复查记录标签
    getReviewRecordsLabel() {
      if (!this.detailData || !this.detailData.errorReview) {
        return '复查记录(无)';
      }
      return '复查记录';
    },
    // 获取复查状态文本
    getReviewStateText(state) {
      const stateMap = {
        0: '未整改',
        1: '部分整改',
        2: '已整改'
      };
      return stateMap[state] || '-';
    },
    // 预览复查图片(需要添加前缀)
    handlePreviewReviewImages(imgArray) {
      if (!imgArray || imgArray.length === 0) {
        this.$message.warning("暂无图片");
        return;
      }
      // 图片没有前缀,需要根据实际情况添加图片服务器地址前缀
      // 假设图片服务器地址为 process.env.VUE_APP_IMG_BASE_URL 或者其他配置
      const baseUrl = process.env.VUE_APP_IMG_BASE_URL || '';
      this.previewImages = imgArray.map((img) => {
        // 如果图片已经是完整URL,直接返回;否则添加前缀
        if (img.startsWith('http://') || img.startsWith('https://')) {
          return img;
        }
        return baseUrl + img;
      });
      this.previewVisible = true;
    },
    // 获取问题数量名称(取第一条数据的 name)
    getNumberFactorName() {
      if (this.detailData && this.detailData.numberFactors && this.detailData.numberFactors.length > 0) {
        try {
          const firstItem = typeof this.detailData.numberFactors[0] === 'string'
            ? JSON.parse(this.detailData.numberFactors[0])
            : this.detailData.numberFactors[0];
          return firstItem.name || '-';
        } catch (error) {
          console.error('解析 numberFactors 失败:', error);
          return '-';
        }
      }
      return '-';
    },
    // 获取扣除分数(取第一条数据的 deductPoint)
    getNumberFactorDeductPoint() {
      if (this.detailData && this.detailData.numberFactors && this.detailData.numberFactors.length > 0) {
        try {
          const firstItem = typeof this.detailData.numberFactors[0] === 'string'
            ? JSON.parse(this.detailData.numberFactors[0])
            : this.detailData.numberFactors[0];
          const deductPoint = firstItem.deductPoint;
          return deductPoint !== undefined && deductPoint !== null ? deductPoint : '-';
        } catch (error) {
          console.error('解析 numberFactors 失败:', error);
          return '-';
        }
      }
      return '-';
    },
    // 获取附加加分(取第一条数据的 additionPoint)
    getNumberFactorAdditionPoint() {
      if (this.detailData && this.detailData.numberFactors && this.detailData.numberFactors.length > 0) {
        try {
          const firstItem = typeof this.detailData.numberFactors[0] === 'string'
            ? JSON.parse(this.detailData.numberFactors[0])
            : this.detailData.numberFactors[0];
          const additionPoint = firstItem.additionPoint;
          return additionPoint !== undefined && additionPoint !== null ? additionPoint : '-';
        } catch (error) {
          console.error('解析 numberFactors 失败:', error);
          return '-';
        }
      }
      return '-';
    },
    // 返回
    backFun() {
      this.$router.go(-1);
    },
    // 分页-每页条数改变
    handleSizeChange(val) {
      console.log(`每页 ${val} 条`);
      this.pageSize = val;
      this.pageNum = 1;
      this.loadTableData();
    },
    // 分页-当前页改变
    handleCurrentChange(val) {
      console.log(`当前页: ${val}`);
      this.pageNum = val;
      this.loadTableData();
    },
  },
};
@@ -552,6 +996,10 @@
.color1 {
  color: rgba(255, 255, 255, 0.9);
}
.min-h--60 {
  min-height: 60px;
}
.bgcolor1 {
@@ -628,6 +1076,10 @@
  }
  ::v-deep .el-input__suffix {
    display: none;
  }
  ::v-deep .el-input__prefix {
    display: none;
  }
@@ -747,4 +1199,4 @@
    opacity: 0;
  }
}
</style>
</style>
screen/src/view/hiddenDanger/service.js
New file
@@ -0,0 +1,25 @@
import request from '@/utils/request'
// 获取项目-委托方列表
export const getLoginNameVoList = async (params) => {
  return request(`/project/getLoginNameVoList`, {
    method: 'get', params
  })
}
// 获取隐患列表
export const getHiddenDangerList = async (data) => {
  return request(`/project/getHiddenDangerList`, {
    method: 'post', data
  })
}
// 获取隐患详情
export const getHiddenDangerDetail = async (problemId) => {
  return request(`/project/getHiddenDangerDetail/${problemId}`, {
    method: 'get'
  })
}
// 编辑隐患责任人
export const editHiddenDangerResponsiblePerson = async (data) => {
  return request(`/project/editHiddenDangerResponsiblePerson`, {
    method: 'post', data
  })
}
screen/src/view/planOfAudit/api.txt
File was deleted