hejianhao
2026-01-22 c7ff06ad7df9475c5dbfd24de9759354c437111d
Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/smartBuilding
5个文件已添加
6个文件已修改
1722 ■■■■■ 已修改文件
screen/src/assets/img/project/mark.png 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/utils/riskSourceMap.js 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/majorRisk/index.vue 285 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/majorRisk/service.js 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/personnelOnDuty/index.vue 346 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/personnelOnDuty/service.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/planOfAudit/api.txt 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/planOfAudit/index.vue 568 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/planOfAudit/service.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/projectHome/index.vue 350 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/projectHome/service.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/assets/img/project/mark.png
screen/src/utils/riskSourceMap.js
@@ -51,11 +51,56 @@
export const projectHazardSourceMap = {
  HIGH_ALTITUDE_OPERATION: '高处作业',
  FIRE_OPERATION: '动火作业',
  MASS_CONCRETE_POURING: '大体积混凝土浇筑',
  CONFINED_SPACE_OPERATION: '有限空间作业',
  ROAD_OCCUPATION_CONSTRUCTION: '占道施工',
  LARGE_CONCRETE_CONSTRUCTION: '大体积混凝土浇筑',
  LIMITED_SPACE_OPERATION: '有限空间作业',
  OCCUPY_CONSTRUCTION: '占道施工',
  SINGLE_PERSON_OPERATION: '单人操作及作业',
  SPACE_CROSS_OPERATION: '空间交叉作业'
  SPACE_INTERSECTION_OPERATION: '空间交叉作业'
}
// 项目类型映射
export const projectTypeMapNew = {
  HOUSE_CONSTRUCTION: '房建工程',
  MUNICIPAL_ADMINISTRATION: '市政工程',
  FIXED_ASSETS: '固定资产'
}
// 项目用途类型映射
export const projectUsageTypeMapNew = {
  RESIDENTIAL: '住宅',
  COMMERCIAL: '商业',
  ROAD: '道路',
  BRIDGE: '桥梁',
  TUNNEL_WORKS: '隧道工程',
  RETAIL: '仓储',
  FOOD: '餐饮',
  EDUCATION: '教育',
  MEDICAL_BEAUTY: '医疗美容',
  ENTERTAINMENT: '娱乐',
  HOTEL: '酒店',
  REFIT: '翻新改造',
  PUBLIC_PLACE: '公共场所',
  ENERGY_STATION: '能源站',
  MULTI_FUNCTION_COMPLEX: '综合体'
}
export const planTypeMap = {
  SECURITY_CHECK: '安全检查',
  QUALITY_CHECK: '质量检查',
  COMPREHENSIVE_CHECK: '综合检查',
  EQUIPMENT_CHECK: '设备检查',
  OPERATION_CHECK: '运营检查'
}
export const errorLevelMap = {
  P1: '一级隐患',
  P2: '二级隐患',
  P3: '三级隐患',
  P4: '四级隐患'
}
export const errorReviewStateMap = {
  NOT_REFORMED: '未整改',
  PARTIAL_REFORMED: '部分整改',
  REFORMED: '已整改'
}
/**
@@ -94,4 +139,3 @@
    }
  })
}
screen/src/view/majorRisk/index.vue
@@ -12,12 +12,13 @@
      </div>
      <!-- 搜索 -->
      <div class="flex a-center gap--16 mt--24 mb--20">
        <!-- 开始时间 -->
        <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="startTime"
            ref="datetime1"
            type="datetime"
            placeholder=""
@@ -26,27 +27,28 @@
          <div
            class="absolute w100 h100 flex a-center c-fff fs--14 SHS"
            style="width: calc(100% - 20px)"
            v-if="dateTxt"
            v-if="startTimeTxt"
          >
            {{ dateTxt }}
            {{ startTimeTxt }}
          </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="endTime"
            ref="datetime2"
            type="datetime"
            placeholder=""
@@ -55,64 +57,72 @@
          <div
            class="absolute w100 h100 flex a-center c-fff fs--14 SHS"
            style="width: calc(100% - 20px)"
            v-if="dateTxt"
            v-if="endTimeTxt"
          >
            {{ dateTxt }}
            {{ endTimeTxt }}
          </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="searchContent"
            placeholder="请输入搜索内容"
            class="flex1"
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
            >
            </el-option>
          </el-select>
          <div class="btn SHSR">搜索</div>
            @keyup.enter.native="handleSearch"
          />
          <div @click="handleSearch" class="btn SHSR">搜索</div>
        </div>
      </div>
      <!-- 表格 -->
      <CustomTable
        :data="[{ projectName: '项目777' }, { projectName: '项目2' }]"
        :data="tableData"
        v-loading="loading"
        height="800"
        style="border: 2px solid #3d7fff"
      >
        <el-table-column label="序号">
          <template #default="{ row, $index }">
            {{ $index + 1 }}
        <el-table-column label="序号" width="80">
          <template #default="{ $index }">
            {{ (currentPage - 1) * pageSize + $index + 1 }}
          </template>
        </el-table-column>
        <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="操作">
        <el-table-column label="更新时间" prop="updateTime" width="180" />
        <el-table-column label="项目详情" prop="detail" show-overflow-tooltip />
        <el-table-column label="形象进度" prop="progress" width="150" />
        <el-table-column label="危大工程" width="150">
          <template #default="{ row }">
            <el-button
              @click="detailVisible = true"
              type="text"
              class="btnColor"
            {{ formatRiskData(row.dangerousOptions, "dangerous") }}
          </template>
        </el-table-column>
        <el-table-column label="超危大工程" width="150">
          <template #default="{ row }">
            {{ formatRiskData(row.superDangerousOptions, "superDangerous") }}
          </template>
        </el-table-column>
        <el-table-column label="风险源" width="150">
          <template #default="{ row }">
            {{ formatRiskData(row.hazardSource, "hazardSource") }}
          </template>
        </el-table-column>
        <el-table-column
          label="近一季度施工进展情况"
          prop="progressThisSeason"
          show-overflow-tooltip
        />
        <el-table-column label="操作" width="100" fixed="right">
          <template #default="{ row }">
            <el-button @click="showDetail(row)" type="text" class="btnColor"
              >详情</el-button
            >
          </template>
@@ -122,19 +132,19 @@
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="currentPage"
        :page-sizes="[100, 200, 300, 400]"
        :page-size="10"
        :total="400"
        :page-sizes="[10, 20, 50, 100]"
        :page-size="pageSize"
        :total="total"
      />
    </div>
    <!-- 详情弹窗 -->
    <CustomDialog title="项目详情" :visible.sync="detailVisible" width="1420px">
      <div class="oa" style="height: 700px">
      <div class="oa" style="height: 700px" v-if="detailData">
        <div class="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 bgcolor8">
              计划名称计划名称计划名称
              {{ detailData.detail || "暂无数据" }}
            </div>
          </div>
        </div>
@@ -142,7 +152,7 @@
          <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 bgcolor8">
              计划名称计划名称计划名称
              {{ detailData.progress || "暂无数据" }}
            </div>
          </div>
        </div>
@@ -150,7 +160,16 @@
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">危大工程:</div>
            <div class="flex a-center oa gap--14 flex-warp">
              <div v-for="item in 10" :key="item" class="tagItem">111</div>
              <template v-if="parseRiskArray(detailData.dangerousOptions, 'dangerous').length > 0">
                <div
                  v-for="(item, index) in parseRiskArray(detailData.dangerousOptions, 'dangerous')"
                  :key="index"
                  class="tagItem"
                >
                  {{ item }}
                </div>
              </template>
              <div v-else class="c-fff fs--14 SHSR">暂无数据</div>
            </div>
          </div>
        </div>
@@ -158,7 +177,33 @@
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">超危大工程:</div>
            <div class="flex a-center oa gap--14 flex-warp">
              <div v-for="item in 10" :key="item" class="tagItem">111</div>
              <template v-if="parseRiskArray(detailData.superDangerousOptions, 'superDangerous').length > 0">
                <div
                  v-for="(item, index) in parseRiskArray(detailData.superDangerousOptions, 'superDangerous')"
                  :key="index"
                  class="tagItem"
                >
                  {{ item }}
                </div>
              </template>
              <div v-else class="c-fff fs--14 SHSR">暂无数据</div>
            </div>
          </div>
        </div>
        <div class="mt--30">
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">风险源:</div>
            <div class="flex a-center oa gap--14 flex-warp">
              <template v-if="parseRiskArray(detailData.hazardSource, 'hazardSource').length > 0">
                <div
                  v-for="(item, index) in parseRiskArray(detailData.hazardSource, 'hazardSource')"
                  :key="index"
                  class="tagItem"
                >
                  {{ item }}
                </div>
              </template>
              <div v-else class="c-fff fs--14 SHSR">暂无数据</div>
            </div>
          </div>
        </div>
@@ -168,7 +213,7 @@
              近一季度施工进展情况:
            </div>
            <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor8">
              计划名称计划名称计划名称
              {{ detailData.progressThisSeason || "暂无数据" }}
            </div>
          </div>
        </div>
@@ -176,10 +221,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 bgcolor8">
              2020-12-12
              {{ detailData.updateTime || "暂无数据" }}
            </div>
          </div>
        </div>
      </div>
      <div v-else class="flex a-center j-center" style="height: 700px">
        <div class="c-fff fs--16 SHSR">暂无数据</div>
      </div>
    </CustomDialog>
  </div>
@@ -188,25 +236,52 @@
<script>
import HeaderBox from "../header/index.vue";
import moment from "moment";
import {
  getProjectDangerousSourceList,
  getProjectDangerousSourceDetail,
} from "./service";
import { getRiskSourceName } from "@/utils/riskSourceMap.js";
export default {
  components: { HeaderBox },
  props: {},
  data() {
    return {
      dateTime: null,
      startTime: null, // 开始时间
      endTime: null, // 结束时间
      startTimeTxt: "", // 开始时间显示文本
      endTimeTxt: "", // 结束时间显示文本
      projectId: null,
      projectName: null,
      dateTxt: "",
      searchContent: "", // 搜索内容
      currentPage: 1,
      pageSize: 10,
      total: 0,
      tableData: [], // 表格数据
      loading: false, // 加载状态
      detailVisible: false,
      detailData: null, // 详情数据
      currentDetailId: null, // 当前查看详情的ID
    };
  },
  computed: {},
  watch: {
    dateTime: {
      handler(newVal, oldVal) {
    startTime: {
      handler(newVal) {
        if (newVal) {
          this.dateTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
          this.startTimeTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
        } else {
          this.startTimeTxt = "";
        }
      },
      immediate: true,
    },
    endTime: {
      handler(newVal) {
        if (newVal) {
          this.endTimeTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
        } else {
          this.endTimeTxt = "";
        }
      },
      immediate: true,
@@ -216,20 +291,118 @@
    this.projectId = this.$route.query.projectId;
    this.projectName = this.$route.query.projectName;
  },
  mounted() {},
  mounted() {
    // 页面加载时获取列表数据
    this.getList();
  },
  methods: {
    // 获取列表数据
    async getList() {
      try {
        this.loading = true;
        const params = {
          pageNum: this.currentPage,
          pageSize: this.pageSize,
          projectId: this.projectId,
        };
        // 添加可选参数
        if (this.startTime) {
          params.startTime = moment(this.startTime).format(
            "YYYY-MM-DD HH:mm:ss"
          );
        }
        if (this.endTime) {
          params.endTime = moment(this.endTime).format("YYYY-MM-DD HH:mm:ss");
        }
        if (this.searchContent) {
          params.content = this.searchContent;
        }
        const res = await getProjectDangerousSourceList(params);
        if (res) {
          this.tableData = (res && res.records) || (res && res.list) || [];
          this.total = (res && res.total) || 0;
        }
      } catch (error) {
        console.error("获取重大风险源列表失败:", error);
        this.$message.error("获取列表数据失败");
      } finally {
        this.loading = false;
      }
    },
    // 格式化风险数据(用于表格显示)
    formatRiskData(dataStr, type) {
      if (!dataStr) return "-";
      try {
        const dataArray = JSON.parse(dataStr);
        if (!Array.isArray(dataArray) || dataArray.length === 0) return "-";
        // 转换为中文名称并用顿号连接
        const names = dataArray.map(code => getRiskSourceName(type, code));
        return names.join("、");
      } catch (e) {
        console.error("解析风险数据失败:", e);
        return "-";
      }
    },
    // 解析风险数组(用于详情弹窗标签展示)
    parseRiskArray(dataStr, type) {
      if (!dataStr) return [];
      try {
        const dataArray = JSON.parse(dataStr);
        if (!Array.isArray(dataArray)) return [];
        // 转换为中文名称数组
        return dataArray.map(code => getRiskSourceName(type, code));
      } catch (e) {
        console.error("解析风险数据失败:", e);
        return [];
      }
    },
    // 搜索
    handleSearch() {
      this.currentPage = 1; // 重置到第一页
      this.getList();
    },
    // 每页条数改变
    handleSizeChange(val) {
      console.log(`每页 ${val} 条`);
      this.pageSize = val;
      this.currentPage = 1;
      this.getList();
    },
    // 当前页改变
    handleCurrentChange(val) {
      console.log(`当前页: ${val}`);
      this.currentPage = val;
      this.getList();
    },
    // 点击日期选择器
    handleClick(type) {
      this.$refs[`datetime${type}`].focus();
    },
    // 查看详情
    async showDetail(row) {
      try {
        this.currentDetailId = row.id;
        const res = await getProjectDangerousSourceDetail(row.id);
        if (res) {
          this.detailData = res;
          this.detailVisible = true;
        }
      } catch (error) {
        console.error("获取详情失败:", error);
        this.$message.error("获取详情数据失败");
      }
    },
    // 返回
    backFun() {
      this.$router.go(-1);
    },
@@ -336,6 +509,10 @@
    border: 1px solid #307ad9;
    box-sizing: border-box;
    cursor: pointer;
    &:hover {
      opacity: 0.8;
    }
  }
}
screen/src/view/majorRisk/service.js
New file
@@ -0,0 +1,14 @@
import request from '@/utils/request'
// 获取项目重大风险源列表
export const getProjectDangerousSourceList = async (data) => {
  return request(`/project/getProjectDangerousSourceList`, {
    method: 'post', data
  })
}
// 获取项目重大风险源详情
export const getProjectDangerousSourceDetail = async (id) => {
  return request(`/project/getProjectDangerousSourceDetail/${id}`, {
    method: 'get'
  })
}
screen/src/view/personnelOnDuty/index.vue
@@ -17,15 +17,17 @@
        >
          <el-select
            filterable
            v-model="projectId"
            v-model="planId"
            placeholder="请选择检查计划"
            class="flex1"
            clearable
            @change="handleSearch"
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
              v-for="item in planList"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            >
            </el-option>
          </el-select>
@@ -39,15 +41,17 @@
        >
          <el-select
            filterable
            v-model="projectId"
            v-model="positionId"
            placeholder="请选择岗位"
            class="flex1"
            clearable
            @change="handleSearch"
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
              v-for="item in positionList"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            >
            </el-option>
          </el-select>
@@ -61,24 +65,25 @@
          class="flex a-center j-between px--20 w--300 h--40 relative bgcolor1 picker"
        >
          <el-date-picker
            v-model="dateTime"
            v-model="startTime"
            ref="datetime1"
            type="datetime"
            placeholder=""
            @change="handleSearch"
          >
          </el-date-picker>
          <div
            class="absolute w100 h100 flex a-center c-fff fs--14 SHS"
            style="width: calc(100% - 20px)"
            v-if="dateTxt"
            v-if="startTimeTxt"
          >
            {{ dateTxt }}
            {{ startTimeTxt }}
          </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"
@@ -90,24 +95,25 @@
          class="flex a-center j-between px--20 w--300 h--40 relative bgcolor1 picker"
        >
          <el-date-picker
            v-model="dateTime"
            v-model="endTime"
            ref="datetime2"
            type="datetime"
            placeholder=""
            @change="handleSearch"
          >
          </el-date-picker>
          <div
            class="absolute w100 h100 flex a-center c-fff fs--14 SHS"
            style="width: calc(100% - 20px)"
            v-if="dateTxt"
            v-if="endTimeTxt"
          >
            {{ dateTxt }}
            {{ endTimeTxt }}
          </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"
@@ -117,74 +123,65 @@
        <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="searchText"
            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' },
          { projectName: '项目4' },
        ]"
        :data="tableData"
        height="800"
        style="border: 2px solid #3d7fff"
        v-loading="loading"
      >
        <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">
          <template #default="{ row }"
            ><div
        <el-table-column label="计划名称" prop="planName" min-width="120" />
        <el-table-column label="检查内容" prop="checkContent" min-width="150" show-overflow-tooltip />
        <el-table-column label="岗位" prop="postName" min-width="100" />
        <el-table-column label="问题备注" prop="remark" min-width="150" show-overflow-tooltip />
        <el-table-column label="图片" min-width="80">
          <template #default="{ row }">
            <div
              class="btnColor cursor-pointer"
              :class="false && 'disableColor'"
              @click="previewVisible = true"
              :class="!row.img && 'disableColor'"
              @click="row.img && previewImage(row)"
            >
              1张
              {{ getImageCount(row.img) }}张
            </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="time" min-width="160" />
        <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', 'bgColor66'][$index + 1]"
                  :class="stateMap[row.state] && stateMap[row.state].colorClass[0]"
                ></div>
                <div
                  class="ripple-wave wave-2"
                  :class="['bgColor44', 'bgColor77'][$index + 1]"
                  :class="stateMap[row.state] && stateMap[row.state].colorClass[1]"
                ></div>
                <div
                  class="ripple-wave wave-3"
                  :class="['bgColor55', 'bgColor88'][$index + 1]"
                  :class="stateMap[row.state] && stateMap[row.state].colorClass[2]"
                ></div>
              </div>
              <div class="ml--6 SHSR">{{ $index }}</div>
              <div class="ml--6 SHSR">{{ stateMap[row.state] ? stateMap[row.state].text : '-' }}</div>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="操作">
        <el-table-column label="操作" width="100">
          <template #default="{ row }">
            <el-button
              @click="detailVisible = true"
              @click="showDetail(row)"
              type="text"
              class="btnColor"
              >详情</el-button
@@ -196,25 +193,25 @@
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="currentPage"
        :page-sizes="[100, 200, 300, 400]"
        :page-size="10"
        :total="400"
        :page-sizes="[10, 20, 50, 100]"
        :page-size="pageSize"
        :total="total"
      />
    </div>
    <!-- 详情弹窗 -->
    <CustomDialog title="项目详情" :visible.sync="detailVisible" width="1420px">
      <div class="oa" style="height: 700px">
    <CustomDialog title="人员在岗详情" :visible.sync="detailVisible" width="1420px">
      <div class="oa" style="height: 700px" v-loading="detailLoading">
        <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 bgcolor8">
              计划名称计划名称计划名称
              {{ detailData.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 bgcolor8">
              计划名称计划名称计划名称
              {{ projectName || '-' }}
            </div>
          </div>
        </div>
@@ -222,13 +219,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 bgcolor8">
              计划名称计划名称计划名称
              {{ detailData.checkContent || '-' }}
            </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 bgcolor8">
              计划名称计划名称计划名称
              {{ detailData.postName || '-' }}
            </div>
          </div>
        </div>
@@ -236,13 +233,27 @@
          <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 bgcolor8">
              计划名称计划名称计划名称
              {{ detailData.remark || '-' }}
            </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 bgcolor8">
              计划名称计划名称计划名称
            <div class="lh--60 px--19 c-fff SHSBold fs--18 w100 bgcolor8 flex a-center">
              <div class="ripple mr--10" v-if="detailData.state">
                <div
                  class="ripple-wave wave-1"
                  :class="stateMap[detailData.state] && stateMap[detailData.state].colorClass[0]"
                ></div>
                <div
                  class="ripple-wave wave-2"
                  :class="stateMap[detailData.state] && stateMap[detailData.state].colorClass[1]"
                ></div>
                <div
                  class="ripple-wave wave-3"
                  :class="stateMap[detailData.state] && stateMap[detailData.state].colorClass[2]"
                ></div>
              </div>
              {{ stateMap[detailData.state] ? stateMap[detailData.state].text : '-' }}
            </div>
          </div>
        </div>
@@ -250,7 +261,7 @@
          <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 bgcolor8">
              2020-12-12
              {{ detailData.time || '-' }}
            </div>
          </div>
        </div>
@@ -259,11 +270,14 @@
            <div class="SHSR fs--16 lh--16 mb--17 color1">图片:</div>
            <div class="flex a-center oa gap--14">
              <img
                v-for="item in 6"
                :key="item"
                src="../../assets/img/home/@3x@2x.png"
                class="w--150 h--120"
                v-for="(item, index) in detailData.img ? detailData.img.split(',').filter(i => i) : []"
                :key="index"
                :src="item"
                class="w--150 h--120 cursor-pointer"
                style="object-fit: cover; border-radius: 4px;"
                @click="previewImage(detailData)"
              />
              <div v-if="!detailData.img || !detailData.img.split(',').filter(i => i).length" class="c-fff SHSR">暂无图片</div>
            </div>
          </div>
        </div>
@@ -272,7 +286,7 @@
    <!-- 图片预览 -->
    <CustomPreview
      :show="previewVisible"
      :imgArr="[]"
      :imgArr="previewImages"
      @close="previewVisible = false"
    />
  </div>
@@ -281,26 +295,67 @@
<script>
import HeaderBox from "../header/index.vue";
import moment from "moment";
import { getPositionList, getPlanList, getProjectOnDutyList, getProjectOnDutyDetail } from "./service";
export default {
  components: { HeaderBox },
  props: {},
  data() {
    return {
      dateTime: null,
      startTime: null,
      endTime: null,
      projectId: null,
      projectName: null,
      dateTxt: "",
      startTimeTxt: "",
      endTimeTxt: "",
      currentPage: 1,
      pageSize: 10,
      total: 0,
      detailVisible: false,
      previewVisible: false,
      // 下拉框数据
      planList: [], // 计划列表
      positionList: [], // 岗位列表
      planId: null, // 选中的计划ID
      positionId: null, // 选中的岗位ID
      searchText: "", // 搜索内容
      // 表格数据
      tableData: [],
      loading: false,
      // 详情数据
      detailData: {},
      detailLoading: false,
      // 图片预览数据
      previewImages: [],
    };
  },
  computed: {},
  computed: {
    // 在岗情况映射
    stateMap() {
      return {
        1: { text: "在岗", colorClass: ["bgColor33", "bgColor44", "bgColor55"] },
        2: { text: "未在岗", colorClass: ["wave-1", "wave-2", "wave-3"] },
        3: { text: "请假", colorClass: ["bgColor66", "bgColor77", "bgColor88"] },
      };
    },
  },
  watch: {
    dateTime: {
      handler(newVal, oldVal) {
    startTime: {
      handler(newVal) {
        if (newVal) {
          this.dateTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
          this.startTimeTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
        } else {
          this.startTimeTxt = "";
        }
      },
      immediate: true,
    },
    endTime: {
      handler(newVal) {
        if (newVal) {
          this.endTimeTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
        } else {
          this.endTimeTxt = "";
        }
      },
      immediate: true,
@@ -310,20 +365,144 @@
    this.projectId = this.$route.query.projectId;
    this.projectName = this.$route.query.projectName;
  },
  mounted() {},
  mounted() {
    this.initData();
  },
  methods: {
    // 初始化数据
    async initData() {
      await this.getPlanListData();
      await this.getPositionListData();
      await this.getTableData();
    },
    // 获取计划下拉框数据
    async getPlanListData() {
      try {
        const res = await getPlanList();
        if (res) {
          this.planList = res || [];
        }
      } catch (error) {
        console.error("获取计划列表失败:", error);
      }
    },
    // 获取岗位下拉框数据
    async getPositionListData() {
      try {
        const res = await getPositionList();
        if (res) {
          this.positionList = res || [];
        }
      } catch (error) {
        console.error("获取岗位列表失败:", error);
      }
    },
    // 获取表格数据
    async getTableData() {
      this.loading = true;
      try {
        const params = {
          pageNum: this.currentPage,
          pageSize: this.pageSize,
          projectId: this.projectId,
        };
        // 添加可选参数
        if (this.planId) params.planId = this.planId;
        if (this.positionId) params.positionId = this.positionId;
        if (this.startTime) {
          params.startTime = moment(this.startTime).format(
            "YYYY-MM-DD HH:mm:ss"
          );
        }
        if (this.endTime) {
          params.endTime = moment(this.endTime).format("YYYY-MM-DD HH:mm:ss");
        }
        if (this.searchText) params.remark = this.searchText;
        const res = await getProjectOnDutyList(params);
        if (res) {
          this.tableData = res.records || res.list || [];
          this.total = res.total || 0;
        }
      } catch (error) {
        console.error("获取列表数据失败:", error);
        this.$message.error("获取列表数据失败");
      } finally {
        this.loading = false;
      }
    },
    // 搜索
    handleSearch() {
      this.currentPage = 1;
      this.getTableData();
    },
    // 分页大小改变
    handleSizeChange(val) {
      console.log(`每页 ${val} 条`);
      this.pageSize = val;
      this.currentPage = 1;
      this.getTableData();
    },
    // 当前页改变
    handleCurrentChange(val) {
      console.log(`当前页: ${val}`);
      this.currentPage = val;
      this.getTableData();
    },
    // 点击日期选择器
    handleClick(type) {
      this.$refs[`datetime${type}`].focus();
    },
    // 查看详情
    async showDetail(row) {
      if (!row.id) {
        this.$message.warning("数据异常,无法查看详情");
        return;
      }
      this.detailVisible = true;
      this.detailLoading = true;
      try {
        const res = await getProjectOnDutyDetail(row.id);
        if (res) {
          this.detailData = res;
        } else {
          this.$message.error("获取详情失败");
        }
      } catch (error) {
        console.error("获取详情失败:", error);
        this.$message.error("获取详情失败");
      } finally {
        this.detailLoading = false;
      }
    },
    // 预览图片
    previewImage(row) {
      if (row.img) {
        // 如果img是字符串,可能是逗号分隔的多张图片
        this.previewImages = row.img.split(",").filter((item) => item);
        this.previewVisible = true;
      } else {
        this.$message.warning("暂无图片");
      }
    },
    // 获取图片数量
    getImageCount(img) {
      if (!img) return 0;
      return img.split(",").filter((item) => item).length;
    },
    // 返回
    backFun() {
      this.$router.go(-1);
    },
@@ -433,6 +612,11 @@
  }
}
.disableColor {
  color: #666 !important;
  cursor: not-allowed !important;
}
.btnColor {
  color: #00ebfc;
}
screen/src/view/personnelOnDuty/service.js
New file
@@ -0,0 +1,26 @@
import request from '@/utils/request'
// 获取岗位下拉框信息
export const getPositionList = async (id) => {
  return request(`/project/getPositionList`, {
    method: 'get'
  })
}
// 获取计划下拉框信息
export const getPlanList = async (id) => {
  return request(`/project/getPlanList`, {
    method: 'get'
  })
}
// 获取人员在岗列表
export const getProjectOnDutyList = async (data) => {
  return request(`/project/getProjectOnDutyList`, {
    method: 'post', data
  })
}
// 获取人员在岗详情
export const getProjectOnDutyDetail = async (id) => {
  return request(`/project/getProjectOnDutyDetail/${id}`, {
    method: 'get'
  })
}
screen/src/view/planOfAudit/api.txt
New file
@@ -0,0 +1,45 @@
1、计划下拉框信息 是get 无传参  响应:name 名称; id
2、获取检查列表  post  json传参
    请求参数:pageNum    分页参数,当前页码;pageSize    分页参数,每页数量,默认为10;projectId    项目id
    响应参数: checkTime    检查时间    string
              commentType    1模糊评价 2打分评价    integer
              containDeviceRes    设备报告 1有 0无    integer
              containSystemRes    安全报告 1有 0无    integer
              fuzzyScore    模糊分数    string
              id        integer
              planName    检查计划名称    string
              planType    计划类型    integer
              riskLevel    风险类型    integer
              score    打分分数    string
3、获取检查资料列表 get params传参  请求参数:planId    planId
    响应参数: checkUserImg    检查人员照片    string
              clientId        integer(int32)
              createTime        string(date-time)
              creatorId    创建人id    integer(int32)
              deleted    是否删除 false正常true删除    boolean
              id        integer(int32)
              integrityNotification    廉政告知单    string
              planId        integer(int32)
              planName    计划名称    string
              projectId        integer(int32)
              projectName    项目名称    string
              safetySlip    安全告知单    string
              time    检查时间    string(date-time)
              title    检查标题    string
4、获取检查资料详情 get params传参  请求参数:id    检查资料id
    响应参数:checkUserImg    检查人员照片    string
              clientId        integer(int32)
              createTime        string(date-time)
              creatorId    创建人id    integer(int32)
              deleted    是否删除 false正常true删除    boolean
              id        integer(int32)
              integrityNotification    廉政告知单    string
              planId        integer(int32)
              planName    计划名称    string
              projectId        integer(int32)
              projectName    项目名称    string
              safetySlip    安全告知单    string
              time    检查时间    string(date-time)
              title    检查标题    string
screen/src/view/planOfAudit/index.vue
@@ -17,15 +17,16 @@
        >
          <el-select
            filterable
            v-model="projectId"
            v-model="planType"
            placeholder="请选择计划类型"
            class="flex1"
            clearable
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
              v-for="item in planOptions"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            >
            </el-option>
          </el-select>
@@ -39,7 +40,7 @@
          class="flex a-center j-between px--20 w--300 h--40 relative bgcolor1 picker"
        >
          <el-date-picker
            v-model="dateTime"
            v-model="startTime"
            ref="datetime1"
            type="datetime"
            placeholder=""
@@ -48,15 +49,15 @@
          <div
            class="absolute w100 h100 flex a-center c-fff fs--14 SHS"
            style="width: calc(100% - 20px)"
            v-if="dateTxt"
            v-if="startTimeTxt"
          >
            {{ dateTxt }}
            {{ startTimeTxt }}
          </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"
@@ -68,7 +69,7 @@
          class="flex a-center j-between px--20 w--300 h--40 relative bgcolor1 picker"
        >
          <el-date-picker
            v-model="dateTime"
            v-model="endTime"
            ref="datetime2"
            type="datetime"
            placeholder=""
@@ -77,15 +78,15 @@
          <div
            class="absolute w100 h100 flex a-center c-fff fs--14 SHS"
            style="width: calc(100% - 20px)"
            v-if="dateTxt"
            v-if="endTimeTxt"
          >
            {{ dateTxt }}
            {{ endTimeTxt }}
          </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"
@@ -95,37 +96,54 @@
        <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="searchKeyword"
            placeholder="请输入搜索内容"
            class="flex1"
          >
            <el-option
              v-for="item in [1, 2]"
              :key="item"
              :label="item"
              :value="item"
            >
            </el-option>
          </el-select>
          <div class="btn SHSR">搜索</div>
            clearable
            @keyup.enter.native="handleSearch"
          />
          <div @click="handleSearch" class="btn SHSR">搜索</div>
        </div>
      </div>
      <!-- 表格 -->
      <CustomTable
        :data="[{ projectName: '项目777' }, { projectName: '项目2' }]"
        :data="checkList"
        height="800"
        style="border: 2px solid #3d7fff"
        v-loading="loading"
      >
        <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="操作">
        <el-table-column label="检查计划" prop="planName" min-width="150" />
        <el-table-column label="计划类型" min-width="120">
          <template #default="{ row }">
            {{ formatPlanType(row.planType) }}
          </template>
        </el-table-column>
        <el-table-column label="检查时间" min-width="180">
          <template #default="{ row }">
            {{ formatTime(row.checkTime) }}
          </template>
        </el-table-column>
        <!-- 以下列仅在非项目端(type != 2)时显示 -->
        <el-table-column v-if="type != 2" label="风险等级" min-width="120">
          <template #default="{ row }">
            {{ formatRiskLevel(row.riskLevel) }}
          </template>
        </el-table-column>
        <el-table-column v-if="type != 2" label="评价方式" min-width="120">
          <template #default="{ row }">
            {{ formatCommentType(row.commentType) }}
          </template>
        </el-table-column>
        <el-table-column v-if="type != 2" label="评分" min-width="100">
          <template #default="{ row }">
            {{ getScoreDisplay(row) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" fixed="right">
          <template #default="{ row }">
            <el-button
              @click="projectVisible = true"
              @click="openCheckMaterialDialog(row)"
              type="text"
              class="btnColor"
              >检查资料</el-button
@@ -138,14 +156,16 @@
                class="h100 flex flex-column a-center j-between c-fff fs--16 SHSBold"
              >
                <div
                  @click="openReport('safe')"
                  @click="openReport('safe', row)"
                  class="py--12 w100 txt-center br--10 bgcolor6 cursor-pointer"
                  :class="{ disabled: row.containSystemRes !== 1 }"
                >
                  安全报告
                </div>
                <div
                  @click="openReport('device')"
                  @click="openReport('device', row)"
                  class="py--12 w100 txt-center br--10 bgcolor7 cursor-pointer"
                  :class="{ disabled: row.containDeviceRes !== 1 }"
                >
                  设备报告
                </div>
@@ -158,26 +178,51 @@
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="currentPage"
        :page-sizes="[100, 200, 300, 400]"
        :page-size="10"
        :total="400"
        :page-sizes="[10, 20, 50, 100]"
        :page-size="pageSize"
        :total="total"
      />
    </div>
    <!-- 检查资料列表弹窗 -->
    <CustomDialog title="项目" :visible.sync="projectVisible" width="1420px">
    <CustomDialog
      title="检查资料列表"
      :visible.sync="projectVisible"
      width="1420px"
      @close="closeProjectDialog"
    >
      <CustomTable
        :data="[{ projectName: '项目777' }, { projectName: '项目2' }]"
        :data="checkMaterialList"
        height="630"
        v-loading="loading"
      >
        <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="操作">
        <el-table-column label="检查标题" prop="title" min-width="150" />
        <!-- 以下列仅在非项目端(type != 2)时显示 -->
        <el-table-column v-if="type != 2" label="计划名称" prop="planName" min-width="150" />
        <el-table-column v-if="type != 2" label="项目名称" prop="projectName" min-width="150" />
        <el-table-column label="检查时间" min-width="180">
          <template #default="{ row }">
            {{ formatTime(row.time) }}
          </template>
        </el-table-column>
        <el-table-column label="安全告知单" min-width="100">
          <template #default="{ row }">
            {{ row.safetySlip ? "有" : "无" }}
          </template>
        </el-table-column>
        <el-table-column label="检查人员照片" min-width="120">
          <template #default="{ row }">
            {{ row.checkUserImg ? "有" : "无" }}
          </template>
        </el-table-column>
        <el-table-column label="廉政告知单" min-width="100">
          <template #default="{ row }">
            {{ row.integrityNotification ? "有" : "无" }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="100" fixed="right">
          <template #default="{ row }">
            <el-button
              @click="detailVisible = true"
              @click="openDetailDialog(row)"
              type="text"
              class="btnColor"
              >详情</el-button
@@ -187,7 +232,12 @@
      </CustomTable>
    </CustomDialog>
    <!-- 检查报告弹窗 -->
    <CustomDialog title="检查报告" :visible.sync="reportVisible" width="1420px">
    <CustomDialog
      title="检查报告"
      :visible.sync="reportVisible"
      width="1420px"
      @close="closeReportDialog"
    >
      <div style="height: 691px" class="oa">
        <iframe
          v-if="reportUrl"
@@ -196,6 +246,9 @@
          height="100%"
          frameborder="0"
        ></iframe>
        <div v-else class="flex a-center j-center h100 c-fff fs--16">
          暂无报告数据
        </div>
      </div>
    </CustomDialog>
    <!-- 检查资料详情弹窗 -->
@@ -203,73 +256,98 @@
      title="检查资料详情"
      :visible.sync="detailVisible"
      width="1420px"
      @close="closeDetailDialog"
    >
      <div class="oa" style="height: 700px">
      <div class="oa" style="height: 700px" v-loading="loading">
        <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 bgcolor8">
              计划名称计划名称计划名称
              {{ checkMaterialDetail.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 bgcolor8">
              计划名称计划名称计划名称
              {{ checkMaterialDetail.projectName || "-" }}
            </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 bgcolor8">
              计划名称计划名称计划名称
              {{ checkMaterialDetail.title || "-" }}
            </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 bgcolor8">
              计划名称计划名称计划名称
              {{ formatTime(checkMaterialDetail.time) }}
            </div>
          </div>
        </div>
        <div class="mt--30">
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">安全告知单:</div>
            <div class="flex a-center oa gap--14">
            <div
              class="flex a-center oa gap--14"
              v-if="parseImages(checkMaterialDetail.safetySlip).length > 0"
            >
              <img
                v-for="item in 6"
                :key="item"
                src="../../assets/img/home/@3x@2x.png"
                class="w--150 h--120"
                v-for="(item, index) in parseImages(
                  checkMaterialDetail.safetySlip
                )"
                :key="index"
                :src="item"
                class="w--150 h--120 cursor-pointer"
                @click="previewImage(item)"
              />
            </div>
            <div v-else class="c-fff fs--14 SHSR">暂无图片</div>
          </div>
        </div>
        <div class="mt--30">
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">检查人员照片:</div>
            <div class="flex a-center oa gap--14">
            <div
              class="flex a-center oa gap--14"
              v-if="parseImages(checkMaterialDetail.checkUserImg).length > 0"
            >
              <img
                v-for="item in 6"
                :key="item"
                src="../../assets/img/home/@3x@2x.png"
                class="w--150 h--120"
                v-for="(item, index) in parseImages(
                  checkMaterialDetail.checkUserImg
                )"
                :key="index"
                :src="item"
                class="w--150 h--120 cursor-pointer"
                @click="previewImage(item)"
              />
            </div>
            <div v-else class="c-fff fs--14 SHSR">暂无图片</div>
          </div>
        </div>
        <div class="mt--30">
          <div class="flex1">
            <div class="SHSR fs--16 lh--16 mb--17 color1">廉政告知单:</div>
            <div class="flex a-center oa gap--14">
            <div
              class="flex a-center oa gap--14"
              v-if="
                parseImages(checkMaterialDetail.integrityNotification).length >
                0
              "
            >
              <img
                v-for="item in 6"
                :key="item"
                src="../../assets/img/home/@3x@2x.png"
                class="w--150 h--120"
                v-for="(item, index) in parseImages(
                  checkMaterialDetail.integrityNotification
                )"
                :key="index"
                :src="item"
                class="w--150 h--120 cursor-pointer"
                @click="previewImage(item)"
              />
            </div>
            <div v-else class="c-fff fs--14 SHSR">暂无图片</div>
          </div>
        </div>
      </div>
@@ -280,32 +358,90 @@
<script>
import HeaderBox from "../header/index.vue";
import moment from "moment";
import {
  getPlanList,
  getDeviceDetailCheckList,
  getDeviceDetailCheckPlanList,
  getDeviceDetailCheckPlanDetail,
} from "./service";
import { planTypeMap } from "@/utils/riskSourceMap";
export default {
  components: { HeaderBox },
  props: {},
  data() {
    return {
      dateTime: null,
      // 搜索条件
      planType: "", // 计划类型
      startTime: null, // 开始时间
      endTime: null, // 结束时间
      searchKeyword: "", // 搜索关键词
      startTimeTxt: "",
      endTimeTxt: "",
      // 项目信息
      projectId: null,
      projectName: null,
      dateTxt: "",
      type: null, // 路由参数 type,用于判断是否为项目端
      // 分页
      currentPage: 1,
      pageSize: 10,
      total: 0,
      // 列表数据
      planOptions: [], // 计划类型下拉选项
      checkList: [], // 检查列表
      checkMaterialList: [], // 检查资料列表
      checkMaterialDetail: {}, // 检查资料详情
      // 弹窗控制
      projectVisible: false,
      reportVisible: false,
      detailVisible: false,
      // 当前选中的数据
      currentPlanId: null, // 当前选中的计划ID
      currentCheckId: null, // 当前选中的检查资料ID
      currentRow: null, // 当前选中的行数据
      // 报告相关
      reportUrl: "",
      safeReportUrl:
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
      deviceReportUrl:
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
      loading: false,
      // 枚举映射
      planTypeMap: planTypeMap, // 计划类型映射
      riskLevelMap: {
        0: "拒绝接受风险",
        1: "不可接受风险",
        2: "可接受风险",
        3: "可容忍风险",
        4: "可忽略风险",
      },
      commentTypeMap: {
        1: "模糊评价",
        2: "打分评价",
      },
    };
  },
  computed: {},
  watch: {
    dateTime: {
      handler(newVal, oldVal) {
    startTime: {
      handler(newVal) {
        if (newVal) {
          this.dateTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
          this.startTimeTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
        } else {
          this.startTimeTxt = "";
        }
      },
      immediate: true,
    },
    endTime: {
      handler(newVal) {
        if (newVal) {
          this.endTimeTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
        } else {
          this.endTimeTxt = "";
        }
      },
      immediate: true,
@@ -314,33 +450,273 @@
  created() {
    this.projectId = this.$route.query.projectId;
    this.projectName = this.$route.query.projectName;
    this.type = this.$route.query.type; // 获取路由参数 type
  },
  mounted() {},
  mounted() {
    this.initData();
  },
  methods: {
    // 初始化数据
    async initData() {
      await this.getPlanOptions();
      await this.getCheckList();
    },
    // 获取计划类型下拉选项
    async getPlanOptions() {
      try {
        // 将 planTypeMap 转换为下拉选项格式
        // 下标作为 value 传给后端,中文名称作为 label 显示
        this.planOptions = Object.keys(this.planTypeMap).map((code, index) => ({
          id: index, // 下标用于传给后端
          name: this.planTypeMap[code], // 中文名称用于显示
          code: code, // 保存 code 用于参考
        }));
      } catch (error) {
        console.error("获取计划类型失败:", error);
        this.$message.error("获取计划类型失败");
      }
    },
    // 获取检查列表
    async getCheckList() {
      this.loading = true;
      try {
        const params = {
          pageNum: this.currentPage,
          pageSize: this.pageSize,
          projectId: this.projectId,
        };
        // 添加可选搜索条件
        if (this.planType !== "" && this.planType !== null && this.planType !== undefined) {
          params.planType = this.planType; // 传下标给后端(0, 1, 2...)
        }
        if (this.startTime) {
          params.startTime = moment(this.startTime).format(
            "YYYY-MM-DD HH:mm:ss"
          );
        }
        if (this.endTime) {
          params.endTime = moment(this.endTime).format("YYYY-MM-DD HH:mm:ss");
        }
        if (this.searchKeyword) {
          params.planName = this.searchKeyword; // 计划名称
        }
        const res = await getDeviceDetailCheckList(params);
        if (res && res) {
          this.checkList = res.records || res.list || [];
          this.total = res.total || 0;
        }
      } catch (error) {
        console.error("获取检查列表失败:", error);
        this.$message.error("获取检查列表失败");
      } finally {
        this.loading = false;
      }
    },
    // 搜索
    handleSearch() {
      this.currentPage = 1;
      this.getCheckList();
    },
    // 重置搜索条件
    handleReset() {
      this.planType = "";
      this.startTime = null;
      this.endTime = null;
      this.searchKeyword = "";
      this.currentPage = 1;
      this.getCheckList();
    },
    // 分页 - 每页条数改变
    handleSizeChange(val) {
      console.log(`每页 ${val} 条`);
      this.pageSize = val;
      this.currentPage = 1;
      this.getCheckList();
    },
    // 分页 - 当前页改变
    handleCurrentChange(val) {
      console.log(`当前页: ${val}`);
      this.currentPage = val;
      this.getCheckList();
    },
    // 打开检查资料列表弹窗
    async openCheckMaterialDialog(row) {
      this.currentRow = row;
      this.currentPlanId = row.id;
      this.projectVisible = true;
      await this.getCheckMaterialList();
    },
    // 获取检查资料列表
    async getCheckMaterialList() {
      if (!this.currentPlanId) {
        this.$message.warning("缺少计划ID");
        return;
      }
      this.loading = true;
      try {
        const res = await getDeviceDetailCheckPlanList({
          planId: this.currentPlanId,
        });
        if (res ) {
          this.checkMaterialList = res;
        }
      } catch (error) {
        console.error("获取检查资料列表失败:", error);
        this.$message.error("获取检查资料列表失败");
      } finally {
        this.loading = false;
      }
    },
    // 打开检查资料详情弹窗
    async openDetailDialog(row) {
      this.currentCheckId = row.id;
      this.detailVisible = true;
      await this.getCheckMaterialDetail();
    },
    // 获取检查资料详情
    async getCheckMaterialDetail() {
      if (!this.currentCheckId) {
        this.$message.warning("缺少检查资料ID");
        return;
      }
      this.loading = true;
      try {
        const res = await getDeviceDetailCheckPlanDetail({
          id: this.currentCheckId,
        });
        if (res ) {
          this.checkMaterialDetail = res;
        }
      } catch (error) {
        console.error("获取检查资料详情失败:", error);
        this.$message.error("获取检查资料详情失败");
      } finally {
        this.loading = false;
      }
    },
    // 打开报告
    openReport(type, row) {
      this.currentRow = row;
      if (type === "safe") {
        // 安全报告
        if (row.containSystemRes === 1) {
          this.reportUrl = row.systemReportUrl || "";
        } else {
          this.$message.warning("暂无安全报告");
          return;
        }
      } else if (type === "device") {
        // 设备报告
        if (row.containDeviceRes === 1) {
          this.reportUrl = row.deviceReportUrl || "";
        } else {
          this.$message.warning("暂无设备报告");
          return;
        }
      }
      if (this.reportUrl) {
        this.reportVisible = true;
      } else {
        this.$message.warning("报告地址不存在");
      }
    },
    // 关闭检查资料列表弹窗
    closeProjectDialog() {
      this.projectVisible = false;
      this.checkMaterialList = [];
      this.currentPlanId = null;
      this.currentRow = null;
    },
    // 关闭检查资料详情弹窗
    closeDetailDialog() {
      this.detailVisible = false;
      this.checkMaterialDetail = {};
      this.currentCheckId = null;
    },
    // 关闭报告弹窗
    closeReportDialog() {
      this.reportVisible = false;
      this.reportUrl = "";
    },
    // 日期选择器点击
    handleClick(type) {
      this.$refs[`datetime${type}`].focus();
    },
    // 返回
    backFun() {
      this.$router.go(-1);
    },
    openReport(type) {
      if (type === "safe") {
        this.reportUrl = this.safeReportUrl;
      } else if (type === "device") {
        this.reportUrl = this.deviceReportUrl;
      } else {
        this.reportUrl = "";
    // 格式化时间
    formatTime(time) {
      return time ? moment(time).format("YYYY-MM-DD HH:mm:ss") : "-";
    },
    // 格式化风险等级
    formatRiskLevel(level) {
      return this.riskLevelMap[level] || "-";
    },
    // 格式化计划类型(后端返回的是下标,需要转换为中文名称)
    formatPlanType(type) {
      // 后端返回的是下标,需要根据下标找到对应的 code,再找到中文名称
      const planTypeCodes = Object.keys(this.planTypeMap);
      if (type >= 0 && type < planTypeCodes.length) {
        const code = planTypeCodes[type];
        return this.planTypeMap[code] || "-";
      }
      this.reportVisible = !!this.reportUrl;
      return "-";
    },
    // 格式化评价类型
    formatCommentType(type) {
      return this.commentTypeMap[type] || "-";
    },
    // 获取分数显示
    getScoreDisplay(row) {
      if (row.commentType === 1) {
        return row.fuzzyScore || "-";
      } else if (row.commentType === 2) {
        return row.score || "-";
      }
      return "-";
    },
    // 处理图片数组(字符串转数组)
    parseImages(imgStr) {
      if (!imgStr) return [];
      if (Array.isArray(imgStr)) return imgStr;
      try {
        return imgStr.split(",").filter((item) => item);
      } catch (error) {
        return [];
      }
    },
    // 预览图片
    previewImage(url) {
      // 可以使用 el-image-viewer 或其他图片预览组件
      window.open(url, "_blank");
    },
  },
};
@@ -435,6 +811,13 @@
    display: none;
  }
  ::v-deep .el-input__inner {
    background: transparent;
    border: none;
    color: #fff;
    padding: 0;
  }
  .btn {
    position: absolute;
    top: -2px;
@@ -461,6 +844,11 @@
  color: #00ebfc;
}
.disabled {
  opacity: 0.5;
  cursor: not-allowed !important;
}
::v-deep .el-descriptions {
  .el-descriptions__body {
    background-color: transparent;
screen/src/view/planOfAudit/service.js
New file
@@ -0,0 +1,26 @@
import request from '@/utils/request'
// 获取计划下拉框信息
export const getPlanList = async (id) => {
  return request(`/project/getPlanList`, {
    method: 'get'
  })
}
// 获取检查列表
export const getDeviceDetailCheckList = async (data) => {
  return request(`/project/getDeviceDetailCheckList`, {
    method: 'post', data
  })
}
// 获取检查资料列表
export const getDeviceDetailCheckPlanList = async (params) => {
  return request(`/project/getDeviceDetailCheckPlanList`, {
    method: 'get', params
  })
}
// 获取设备详情检查资料详情
export const getDeviceDetailCheckPlanDetail = async (params) => {
  return request(`/project/getDeviceDetailCheckPlanDetail`, {
    method: 'get', params
  })
}
screen/src/view/projectHome/index.vue
@@ -133,7 +133,7 @@
              </div>
              <div class="info-body">
                <div class="info-item" v-for="(item, index) in dangerTrendList" :key="index">
                  <div class="item-type">{{ item.name || '-' }}</div>
                  <div class="item-type" :title="item.name || '-'">{{ item.name || '-' }}</div>
                  <div class="item-number">
                    <div class="flex a--center">
                      <div class="flex a--center">
@@ -207,62 +207,30 @@
          <template #content>
            <div class="people-info">
              <div class="people-card">
                <div class="people-item" v-if="peopleList.length > 0">
                <div class="people-item" v-for="(item, index) in peopleList.slice(0, 3)" :key="index">
                  <div class="people-chart">
                    <el-progress :width="74" type="circle" :show-text="false" :percentage="25" define-back-color="#000"
                      color="#32C5FF" stroke-linecap="square"></el-progress>
                    <el-progress :width="74" type="circle" :show-text="false" :percentage="parseFloat(item.rate) || 0"
                      define-back-color="#000" color="#32C5FF" stroke-linecap="square"></el-progress>
                    <div class="progress-card">
                      <div class="progress-num">{{ peopleList[0].rate }}</div>
                      <div class="progress-num">{{ item.rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">{{ peopleList[0].postName }}</div>
                </div>
                <div class="people-item" v-if="peopleList.length > 1">
                  <div class="people-chart">
                    <el-progress :width="74" type="circle" :show-text="false" :percentage="25" define-back-color="#000"
                      color="#32C5FF" stroke-linecap="square"></el-progress>
                    <div class="progress-card">
                      <div class="progress-num">{{ peopleList[1].rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">{{ peopleList[1].name }}</div>
                </div>
                <div class="people-item" v-if="peopleList.length > 2">
                  <div class="people-chart">
                    <el-progress :width="74" type="circle" :show-text="false" :percentage="25" define-back-color="#000"
                      color="#32C5FF" stroke-linecap="square"></el-progress>
                    <div class="progress-card">
                      <div class="progress-num">{{ peopleList[2].rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">{{ peopleList[1].name }}</div>
                  <div class="people-name">{{ item.postName || item.name }}</div>
                </div>
              </div>
              <div class="people-card2">
                <div class="people-item mr--67" v-if="peopleList.length > 3">
                <div class="people-item" :class="{ 'mr--67': index === 0 }"
                  v-for="(item, index) in peopleList.slice(3, 5)" :key="index + 3">
                  <div class="people-chart">
                    <el-progress :width="74" type="circle" :show-text="false" :percentage="25" define-back-color="#000"
                      color="#32C5FF" stroke-linecap="square"></el-progress>
                    <el-progress :width="74" type="circle" :show-text="false" :percentage="parseFloat(item.rate) || 0"
                      define-back-color="#000" color="#32C5FF" stroke-linecap="square"></el-progress>
                    <div class="progress-card">
                      <div class="progress-num">{{ peopleList[3].rate }}</div>
                      <div class="progress-num">{{ item.rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">{{ peopleList[1].name }}</div>
                </div>
                <div class="people-item" v-if="peopleList.length > 4">
                  <div class="people-chart">
                    <el-progress :width="74" type="circle" :show-text="false" :percentage="25" define-back-color="#000"
                      color="#32C5FF" stroke-linecap="square"></el-progress>
                    <div class="progress-card">
                      <div class="progress-num">{{ peopleList[4].rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">{{ peopleList[1].name }}</div>
                  <div class="people-name">{{ item.name }}</div>
                </div>
              </div>
            </div>
@@ -313,10 +281,7 @@
        <TitleBox>
          <template #title>项目风险趋势 </template>
          <template #right>
            <div @click="handleClick" class="risk-picker">
              <el-date-picker v-model="dateRange" ref="datePicker" type="monthrange" range-separator=""
                start-placeholder="" end-placeholder="">
              </el-date-picker>
            <div class="risk-picker">
              <div class="risk-ab">
                <img src="../../assets/img/home/Shape@2x (6).png" class="pick-img" />
@@ -324,7 +289,6 @@
                  {{ dateTxt }}
                </div>
              </div>
            </div>
          </template>
          <template #content>
@@ -348,7 +312,8 @@
  deviceTypes,
  getProjectPersonInfo,
  getProjectSource,
  getDangerTrend
  getDangerTrend,
  getProjectDangerousTrend
} from './service.js'
import {
  transformRiskSourceData
@@ -363,7 +328,7 @@
    return {
      mapView: null,
      mapPromise: null,
      dateRange: [moment().startOf("month"), moment().endOf("month")], //默认本月
      dateRange: [moment().subtract(5, 'months').startOf("month"), moment().endOf("month")], //默认近6个月
      markers: [],
      projectId: '',
      projectName: '',
@@ -387,7 +352,12 @@
        { riskScale: '一般', content: '项目风险源内容3', status: '已完成' },
        { riskScale: '重大', content: '项目风险源内容4', status: '未开始' },
        { riskScale: '重大', content: '项目风险源内容5', status: '进行中' },
      ]
      ],
      // 项目风险趋势数据
      riskTrendData: {
        xAxisData: [],
        seriesData: []
      }
    };
  },
  computed: {
@@ -443,6 +413,10 @@
            moment(newVal[0]).format("YYYY -MM") +
            " - " +
            moment(newVal[1]).format("YYYY -MM");
          // 当日期范围改变时,重新获取风险趋势数据
          if (this.projectId) {
            this.getProjectRiskTrend();
          }
        }
      },
      immediate: true,
@@ -458,9 +432,11 @@
      getLoginInfo({ id: this.projectId, type: 2 }).then(res => {
        console.log('res', res)
        this.projectName = res.name
        localStorage.setItem('commentType', res.commentType)
      })
      this.getDetail()
      this.getDangerTrendList()
      this.getProjectRiskTrend()
    }
  },
  mounted() {
@@ -632,6 +608,7 @@
        query: {
          projectId: this.projectId,
          projectName: this.projectName,
          type: 2
        },
      });
    },
@@ -654,6 +631,21 @@
            // mapStyle: "amap://styles/darkblue",
            layers: [satellite],
          });
          // 在地图中心添加marker
          const marker = new AMap.Marker({
            position: [104.065617, 30.65695], // 使用地图中心点坐标
            icon: new AMap.Icon({
              image: require('../../assets/img/project/mark.png'),
              size: new AMap.Size(30, 30),
              imageSize: new AMap.Size(30, 30)
            }),
            offset: new AMap.Pixel(-15, -15) // 设置偏移量,使图标中心对准坐标点
          });
          // 将marker添加到地图
          this.mapView.add(marker);
          // this.initCategoryMarkers(AMap);
        })
        .catch((e) => {
@@ -1079,8 +1071,208 @@
      // });
      return series;
    },
    handleClick() {
      this.$refs.datePicker.focus();
    // 风险等级转换为数值 (A->3, B->2, C->1)
    levelToValue(level) {
      if (!level) return null;
      const levelStr = String(level).toUpperCase().trim();
      // 处理 A、B、C 或 A类、B类、C类
      if (levelStr === 'A' || levelStr === 'A类') return 3;
      if (levelStr === 'B' || levelStr === 'B类') return 2;
      if (levelStr === 'C' || levelStr === 'C类') return 1;
      return null;
    },
    // 数值转换为风险等级显示 (3->A类, 2->B类, 1->C类)
    valueToLevel(value) {
      if (value === 3) return 'A类';
      if (value === 2) return 'B类';
      if (value === 1) return 'C类';
      return '';
    },
    // 获取项目风险趋势数据
    async getProjectRiskTrend() {
      if (!this.dateRange || this.dateRange.length !== 2) {
        return;
      }
      const params = {
        projectId: this.projectId,
        startTime: moment(this.dateRange[0]).format('YYYY-MM'),
        endTime: moment(this.dateRange[1]).format('YYYY-MM')
      };
      try {
        const res = await getProjectDangerousTrend(params);
        console.log('项目风险趋势数据:', res);
        if (res && Array.isArray(res)) {
          // 处理接口返回的数据
          this.riskTrendData.xAxisData = res.map(item => item.time || '');
          // 将A、B、C转换为数值 3、2、1
          this.riskTrendData.seriesData = res.map(item => {
            const value = this.levelToValue(item.level);
            console.log(`level: ${item.level} -> value: ${value}`);
            return value;
          });
          console.log('转换后的数据:', {
            xAxisData: this.riskTrendData.xAxisData,
            seriesData: this.riskTrendData.seriesData
          });
          // 更新图表
          this.updateRiskChart();
        }
      } catch (error) {
        console.error('获取项目风险趋势失败:', error);
      }
    },
    // 更新风险趋势图表
    updateRiskChart() {
      const riskChart = echarts.getInstanceByDom(document.getElementById('riskTrendChart'));
      if (!riskChart) {
        return;
      }
      // 文本换行处理函数
      const formatLabel = (text, maxWidth = 4) => {
        if (!text) return ''
        const textStr = String(text)
        // 如果文本长度超过最大宽度,进行换行
        if (textStr.length > maxWidth) {
          let result = ''
          for (let i = 0; i < textStr.length; i += maxWidth) {
            result += textStr.substring(i, i + maxWidth) + '\n'
          }
          return result.trim()
        }
        return textStr
      }
      const self = this;
      const option = {
        tooltip: {
          trigger: 'axis',
          show: true,
          backgroundColor: 'rgba(0, 0, 0, 0.7)',
          borderColor: '#32C5FF',
          borderWidth: 1,
          textStyle: {
            color: '#fff',
            fontSize: 12
          },
          formatter: function (params) {
            if (params && params.length > 0) {
              const param = params[0];
              const levelText = self.valueToLevel(param.value);
              return `${param.name}<br/>${param.seriesName}: ${levelText}`;
            }
            return '';
          }
        },
        legend: {
          show: false
        },
        grid: {
          top: '10%',
          bottom: '10%',
          left: '10%',
          right: '5%',
          containLabel: true
        },
        xAxis: {
          data: this.riskTrendData.xAxisData,
          axisTick: { show: true, alignWithLabel: true },
          axisLine: {
            lineStyle: {
              color: '#666'
            }
          },
          axisLabel: {
            color: '#fff',
            fontSize: 12,
            interval: 0,
            rotate: 0,
            formatter: function (value) {
              return formatLabel(value, 7)
            },
            align: 'center',
            verticalAlign: 'top',
            lineHeight: 14,
            width: 60,
            overflow: 'break'
          }
        },
        yAxis: {
          type: 'value',
          min: 1,
          max: 3,
          interval: 1,
          splitLine: {
            show: true,
            lineStyle: {
              type: 'dashed',
              color: 'rgba(255, 255, 255, .2)'
            }
          },
          axisLine: {
            show: false
          },
          axisTick: {
            show: false
          },
          axisLabel: {
            color: '#fff',
            fontSize: 12,
            formatter: function (value) {
              // Y轴自定义显示:1->C类, 2->B类, 3->A类
              if (value === 1) return 'C类';
              if (value === 2) return 'B类';
              if (value === 3) return 'A类';
              return '';
            },
            align: 'right',
            verticalAlign: 'middle',
            lineHeight: 14,
            width: 30
          }
        },
        series: [
          {
            name: '风险等级',
            type: 'line',
            data: this.riskTrendData.seriesData,
            smooth: false,
            showSymbol: true,
            symbol: 'circle',
            symbolSize: 8,
            itemStyle: {
              color: '#32C5FF',
              borderColor: '#32C5FF',
              borderWidth: 2
            },
            lineStyle: {
              width: 5,
              color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 1,
                y2: 0,
                colorStops: [
                  { offset: 0, color: 'rgba(36, 81, 242, 1)' },
                  { offset: 0.5, color: 'rgba(11, 204, 254, 1)' },
                  { offset: 1, color: 'rgba(199, 160, 254, 1)' }
                ]
              }
            }
          }
        ]
      };
      riskChart.setOption(option);
    },
    // 初始化折线图
    initRiskChart() {
@@ -1117,7 +1309,7 @@
          containLabel: true
        },
        xAxis: {
          data: ['2025年1月检查', '2025年2月检查', '2025年3月检查', '2025年4月检查', '2025年5月检查'],
          data: this.riskTrendData.xAxisData.length > 0 ? this.riskTrendData.xAxisData : ['2025年1月检查', '2025年2月检查', '2025年3月检查', '2025年4月检查', '2025年5月检查'],
          axisTick: { show: true, alignWithLabel: true },
          axisLine: {
            lineStyle: {
@@ -1144,6 +1336,9 @@
        },
        yAxis: {
          type: 'value',
          min: 1,
          max: 3,
          interval: 1,
          splitLine: {
            show: true,
            lineStyle: {
@@ -1161,24 +1356,25 @@
            color: '#fff',
            fontSize: 12,
            formatter: function (value) {
              // Y轴标签格式化,最多3个字符,超出换行
              const text = String(value)
              return formatLabel(text, 3)
              // Y轴自定义显示:1->C类, 2->B类, 3->A类
              if (value === 1) return 'C类';
              if (value === 2) return 'B类';
              if (value === 3) return 'A类';
              return '';
            },
            // 文本对齐方式
            align: 'right',
            verticalAlign: 'middle',
            lineHeight: 14,
            // 设置标签的最大宽度
            width: 30,
            overflow: 'break' // 超出换行
            width: 30
          }
        },
        series: [
          {
            name: '风险等级',
            type: 'line',
            data: [10, 52, 20, 33, 39],
            data: this.riskTrendData.seriesData.length > 0 ? this.riskTrendData.seriesData : [10, 52, 20, 33, 39],
            smooth: false, // 不使用圆滑过渡
            showSymbol: false, // 不显示转折点
            lineStyle: {
@@ -1246,14 +1442,35 @@
  position: absolute;
  top: 109px;
  left: 20px;
  z-index: 2;
  .project-info {
    padding: 14px;
    height: 204px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    overflow: auto;
    gap: 8px;
    overflow-y: auto;
    overflow-x: hidden;
    // 自定义滚动条样式
    &::-webkit-scrollbar {
      width: 4px;
    }
    &::-webkit-scrollbar-track {
      background: rgba(255, 255, 255, 0.1);
      border-radius: 2px;
    }
    &::-webkit-scrollbar-thumb {
      background: rgba(50, 197, 255, 0.6);
      border-radius: 2px;
      &:hover {
        background: rgba(50, 197, 255, 0.8);
      }
    }
    .flex1 {
      flex: 1;
@@ -1266,6 +1483,7 @@
    .info-item {
      display: flex;
      gap: 10px;
      flex-shrink: 0;
    }
    .label {
@@ -1479,6 +1697,10 @@
        font-size: 12px;
        color: #ffffff;
        line-height: 18px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        cursor: pointer;
      }
      .item-number {
@@ -1580,6 +1802,7 @@
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
  .mid-info {
    cursor: pointer;
@@ -1718,6 +1941,7 @@
  position: absolute;
  top: 109px;
  right: 20px;
  z-index: 3;
  .people-info {
    height: 280px;
screen/src/view/projectHome/service.js
@@ -49,3 +49,11 @@
    method: 'get', params
  })
}
// 获取项目风险趋势
export const getProjectDangerousTrend = async (params) => {
  return request(`/project/getProjectDangerousTrend`, {
    method: 'post',
    data: params
  })
}