董国庆
2026-01-22 ab3b90ded2f995ec6b66e5750cc82e99792c5f09
分项隐患
1个文件已添加
4个文件已修改
477 ■■■■■ 已修改文件
screen/src/view/equipmentList/index.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/projectHome/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/subentry/index.vue 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/subentry/service.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/subentryChart/index.vue 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/equipmentList/index.vue
@@ -128,7 +128,7 @@
        <el-table-column label="操作">
          <template #default="{ row }">
            <el-button @click="toHiddenDanger(row, 3)" type="text" class="btnColor">隐患</el-button>
            <el-button type="text" class="btnColor" @click="projectVisible = true">详情</el-button>
            <el-button type="text" class="btnColor" @click="openDetailDialog(row)">详情</el-button>
          </template>
        </el-table-column>
      </CustomTable>
@@ -137,12 +137,16 @@
    </div>
    <!-- 项目列表弹窗 -->
    <CustomDialog title="项目" :visible.sync="projectVisible" width="1420px">
      <CustomTable :data="[{ projectName: '项目777' }, { projectName: '项目2' }]" height="630">
        <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" />
      <CustomTable :data="detailCheckList" height="630">
        <el-table-column label="检查计划" prop="planName" />
        <el-table-column label="计划类型" prop="planType" />
        <el-table-column label="检测时间" prop="checkTime" />
        <el-table-column label="分数" prop="score">
          <template #default="{ row }">
            <div>{{ getScoreDisplay(row) }}</div>
          </template>
        </el-table-column>
        <el-table-column label="风险类型" prop="riskLevel" />
        <el-table-column label="操作">
          <template #default="{ row }">
            <el-popover trigger="click" popper-class="custom-popper">
@@ -161,6 +165,13 @@
          </template>
        </el-table-column>
      </CustomTable>
      <CustomPagination
        @size-change="handleDetailSizeChange"
        @current-change="handleDetailCurrentChange"
        :current-page="detailCurrentPage"
        :page-sizes="[10, 20, 50, 100]"
        :page-size="detailPageSize"
        :total="detailTotal" />
    </CustomDialog>
    <!-- 检查报告弹窗 -->
    <CustomDialog title="检查报告" :visible.sync="reportVisible" width="1420px">
@@ -201,6 +212,12 @@
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
      deviceTypeList: [],//设备类型列表
      tableList: [],//数据列表
      // 详情弹窗相关数据
      detailCheckList: [],//设备详情检查列表
      detailCurrentPage: 1,
      detailPageSize: 10,
      detailTotal: 0,
      currentDeviceId: null,//当前查看详情的设备ID
    };
  },
  computed: {},
@@ -224,6 +241,16 @@
        }
      },
      immediate: true,
    },
    // 监听弹窗关闭,重置详情数据
    projectVisible(newVal) {
      if (!newVal) {
        this.detailCheckList = [];
        this.detailCurrentPage = 1;
        this.detailPageSize = 10;
        this.detailTotal = 0;
        this.currentDeviceId = null;
      }
    },
  },
  created() {
@@ -362,6 +389,61 @@
      }
      this.reportVisible = !!this.reportUrl;
    },
    // 打开详情弹窗
    openDetailDialog(row) {
      this.currentDeviceId = row.id;
      this.detailCurrentPage = 1;
      this.projectVisible = true;
      this.getDetailCheckList();
    },
    // 获取设备详情检查列表
    getDetailCheckList() {
      if (!this.currentDeviceId) return;
      let params = {
        projectId: this.projectId,
        pageNum: this.detailCurrentPage,
        pageSize: this.detailPageSize
      };
      getDeviceDetailCheckList(params).then(res => {
        console.log('设备详情检查列表', res);
        this.detailCheckList = res.records || [];
        this.detailTotal = res.total || 0;
      }).catch(err => {
        console.error('获取设备详情检查列表失败:', err);
        this.detailCheckList = [];
        this.detailTotal = 0;
      });
    },
    // 详情弹窗分页 - 每页条数改变
    handleDetailSizeChange(val) {
      console.log(`详情每页 ${val} 条`);
      this.detailPageSize = val;
      this.detailCurrentPage = 1;
      this.getDetailCheckList();
    },
    // 详情弹窗分页 - 当前页改变
    handleDetailCurrentChange(val) {
      console.log(`详情当前页: ${val}`);
      this.detailCurrentPage = val;
      this.getDetailCheckList();
    },
    // 根据评价类型显示分数
    getScoreDisplay(row) {
      // commentType: 1模糊评价 2打分评价
      if (row.commentType === 1) {
        return row.fuzzyScore || '-';
      } else if (row.commentType === 2) {
        return row.score || '-';
      }
      return '-';
    },
  },
};
</script>
screen/src/view/projectHome/index.vue
@@ -1202,7 +1202,7 @@
      riskChart.setOption(option)
    },
    toRiskTrend() {
      this.$router.push({ path: '/subentry', query: { projectId: '212222', projectName: '新项目' } })
      this.$router.push({ path: '/subentry', query: { projectId: this.projectId, projectName: this.projectName } })
    }
  }
}
screen/src/view/subentry/index.vue
@@ -38,11 +38,8 @@
            </div>
          </div>
          <div class="flex a-center j-between w--300 pl--20 h--40 relative bgcolor1 searchBox">
            <el-select filterable v-model="projectId" 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>
            <el-input v-model="searchContent" placeholder="请输入搜索内容" class="flex1" clearable />
            <div @click="handleSearch" class="btn SHSR">搜索</div>
          </div>
        </div>
        <div @click="toChart()" class="flex a-center j-center w--150 h--40 cursor-pointer br--4 bgcolor88">
@@ -51,16 +48,30 @@
        </div>
      </div>
      <!-- 表格 -->
      <CustomTable :data="[
        { projectName: '文明施工' },
        { projectName: '文明施工' },
        { projectName: '文明施工' },
      ]" height="800" style="border: 2px solid #3d7fff">
        <el-table-column label="隐患类型" prop="projectName" />
        <el-table-column v-for="(item, index) in tableHeader" :key="index" :label="item.label" :prop="item.prop" />
      <CustomTable
        :data="tableData"
        height="800"
        style="border: 2px solid #3d7fff"
        v-loading="loading"
        element-loading-text="加载中..."
        element-loading-background="rgba(0, 0, 0, 0.8)">
        <el-table-column label="隐患类型" prop="projectName" width="150" />
        <el-table-column
          v-for="(item, index) in tableHeader"
          :key="index"
          :label="item.label"
          :prop="item.prop"
          align="center">
          <template slot-scope="scope">
            {{ scope.row[item.prop] || 0 }}
          </template>
        </el-table-column>
        <template slot="empty">
          <div style="color: #fff; padding: 40px 0;">
            {{ loading ? '加载中...' : '暂无数据,请选择条件后点击搜索' }}
          </div>
        </template>
      </CustomTable>
      <CustomPagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
        :current-page="currentPage" :page-sizes="[100, 200, 300, 400]" :page-size="10" :total="400" />
    </div>
  </div>
</template>
@@ -68,6 +79,7 @@
<script>
import HeaderBox from "../header/index.vue";
import moment from "moment";
import { getTrendList } from "./service";
export default {
  components: { HeaderBox },
  props: {},
@@ -80,13 +92,10 @@
      activeTabs: "本年",
      dateTxt1: "",
      dateTxt2: "",
      currentPage: 1,
      tableHeader: [
        {
          label: "2025年1月",
          prop: "one",
        },
      ],
      searchContent: "",
      tableHeader: [],
      tableData: [],
      loading: false,
    };
  },
  computed: {},
@@ -113,16 +122,8 @@
      this.updateByActiveTab();
    }
  },
  mounted() { },
  mounted() {},
  methods: {
    handleSizeChange(val) {
      console.log(`每页 ${val} 条`);
    },
    handleCurrentChange(val) {
      console.log(`当前页: ${val}`);
    },
    handleClick(type) {
      this.$refs.monthrangeRef.focus();
    },
@@ -144,6 +145,10 @@
        const end = moment().year(lastYear).month(11).startOf("month").toDate();
        this.monthrange = [start, end];
      }
      // 切换标签后自动搜索
      this.$nextTick(() => {
        this.handleSearch();
      });
    },
    generateTableHeader(startDate, endDate) {
@@ -160,6 +165,75 @@
      }
      this.tableHeader = list;
    },
    // 搜索按钮点击事件
    async handleSearch() {
      // 验证必填项
      if (!this.monthrange || this.monthrange.length !== 2) {
        this.$message.warning("请选择日期范围");
        return;
      }
      if (!this.projectId) {
        this.$message.warning("请选择项目");
        return;
      }
      // 构建请求参数
      const params = {
        projectId: this.projectId,
        startTime: moment(this.monthrange[0]).format("YYYY-MM"),
        endTime: moment(this.monthrange[1]).format("YYYY-MM"),
        content: this.searchContent || undefined,
      };
      this.loading = true;
      try {
        const res = await getTrendList(params);
        console.log('11111111111',res)
        if (res) {
          this.processTableData(res || []);
        } else {
          // this.$message.error(res.msg || "获取数据失败");
          this.tableData = [];
        }
      } catch (error) {
        // console.error("获取数据失败:", error);
        // this.$message.error("获取数据失败,请稍后重试");
        this.tableData = [];
      } finally {
        this.loading = false;
      }
    },
    // 处理表格数据
    processTableData(data) {
      if (!data || data.length === 0) {
        this.tableData = [];
        return;
      }
      // 将接口返回的数据转换为表格需要的格式
      // 数据格式为:[{ name: '文明施工', list: [{ num: 10, time: '2025-01' }, ...] }, ...]
      // 需要将每个 name 的 list 数组中的数据按照 time 填充到对应的月份列
      this.tableData = data.map((item) => {
        const row = {
          projectName: item.name,
        };
        // 遍历 list 数组,将每个时间点的数量填充到对应的月份列
        if (item.list && Array.isArray(item.list)) {
          item.list.forEach((listItem) => {
            // 将时间转换为 YYYYMM 格式作为 prop
            const timeProp = moment(listItem.time).format("YYYYMM");
            row[timeProp] = listItem.num;
          });
        }
        return row;
      });
    },
    toChart() {
      this.$router.push({
        path: "/subentryChart",
@@ -295,10 +369,18 @@
    border: none;
    color: #fff;
    padding: 0;
    &::placeholder {
      color: rgba(255, 255, 255, 0.5);
    }
  }
  ::v-deep .el-input__suffix {
    display: none;
    .el-input__suffix-inner {
      .el-icon-circle-close {
        color: #fff;
      }
    }
  }
  .btn {
@@ -318,6 +400,10 @@
    border: 1px solid #307ad9;
    box-sizing: border-box;
    cursor: pointer;
    &:hover {
      opacity: 0.9;
    }
  }
}
screen/src/view/subentry/service.js
New file
@@ -0,0 +1,10 @@
import request from '@/utils/request'
// 各分项隐患趋势列表
export const getTrendList = async (data) => {
  return request(`/project/getProjectHiddenDangerTrendList`, {
    method: 'post',
    data
  })
}
screen/src/view/subentryChart/index.vue
@@ -40,7 +40,13 @@
        </div>
      </div>
      <!-- 折线图-->
      <div ref="dangerChart" class="dangerChart"></div>
      <div
        ref="dangerChart"
        class="dangerChart"
        v-loading="loading"
        element-loading-text="加载中..."
        element-loading-background="rgba(7, 17, 102, 0.8)">
      </div>
    </div>
  </div>
@@ -50,6 +56,8 @@
import HeaderBox from "../header/index.vue";
import moment from "moment";
import * as echarts from "echarts";
import { getTrendList } from "../subentry/service";
export default {
  components: { HeaderBox },
  data() {
@@ -61,11 +69,26 @@
      activeTabs: "本年",
      dateTxt1: "",
      dateTxt2: "",
      searchContent: "",
      chartInstance: null,
      loading: false,
      chartData: {
        xAxisData: [], // X轴月份数据
        series: [], // 系列数据
      },
      // 预设颜色方案,确保图表颜色美观且区分度高
      colorPalette: [
        '#00D9FF', // 青色
        '#FFD700', // 金色
        '#FF6B9D', // 粉红
        '#00FF88', // 翠绿
        '#FF8C42', // 橙色
        '#9D4EDD', // 紫色
        '#06FFA5', // 薄荷绿
        '#FF4757', // 红色
        '#5DADE2', // 天蓝
        '#F39C12', // 琥珀色
      ],
    };
  },
  watch: {
@@ -75,10 +98,16 @@
          this.dateTxt1 = moment(newVal[0]).format("YYYY年MM月");
          this.dateTxt2 = moment(newVal[1]).format("YYYY年MM月");
          this.generateXAxisData(newVal[0], newVal[1]);
          this.fetchChartData();
          // 自动获取数据
          this.$nextTick(() => {
            this.fetchChartData();
          });
        } else {
          this.dateTxt1 = "";
          this.dateTxt2 = "";
          this.chartData.xAxisData = [];
          this.chartData.series = [];
          this.renderChart();
        }
      },
      immediate: true,
@@ -108,13 +137,6 @@
      this.chartInstance = echarts.init(this.$refs.dangerChart);
      this.renderChart();
    },
    // 生成随机颜色
    generateRandomColor() {
      const h = Math.floor(Math.random() * 360);
      const s = Math.floor(Math.random() * 30) + 60; // 60-90%
      const l = Math.floor(Math.random() * 20) + 50; // 50-70%
      return `hsl(${h}, ${s}%, ${l}%)`;
    },
    // 生成X轴数据
    generateXAxisData(startDate, endDate) {
      const start = moment(startDate).startOf("month");
@@ -127,28 +149,87 @@
      }
      this.chartData.xAxisData = list;
    },
    // 获取图表数据(这里使用模拟数据,实际应该调用接口)
    fetchChartData() {
      // TODO: 替换为实际的接口调用
      // 模拟数据
      const seriesNames = ['文明施工', '高处作业', '临时用电', '消防安全', '机械设备'];
    // 获取图表数据
    async fetchChartData() {
      // 验证必填项
      if (!this.monthrange || this.monthrange.length !== 2) {
        this.$message.warning("请选择日期范围");
        return;
      }
      if (!this.projectId) {
        this.$message.warning("项目ID不存在");
        return;
      }
      this.chartData.series = seriesNames.map((name) => {
        const color = this.generateRandomColor();
      // 构建请求参数
      const params = {
        projectId: this.projectId,
        startTime: moment(this.monthrange[0]).format("YYYY-MM"),
        endTime: moment(this.monthrange[1]).format("YYYY-MM"),
        content: this.searchContent || undefined,
      };
      this.loading = true;
      try {
        const res = await getTrendList(params);
        if (res) {
          this.processChartData(res || []);
        } else {
          this.chartData.series = [];
          this.renderChart();
        }
      } catch (error) {
        this.chartData.series = [];
        this.renderChart();
      } finally {
        this.loading = false;
      }
    },
    // 处理图表数据
    processChartData(data) {
      if (!data || data.length === 0) {
        this.chartData.series = [];
        this.renderChart();
        return;
      }
      // 数据格式:[{ name: '文明施工', list: [{ num: 10, time: '2025-01' }, ...] }, ...]
      // 转换为 ECharts 需要的系列格式
      this.chartData.series = data.map((item) => {
        // 创建一个时间到数量的映射
        const timeNumMap = {};
        if (item.list && Array.isArray(item.list)) {
          item.list.forEach((listItem) => {
            const time = moment(listItem.time).format("YYYY-MM");
            timeNumMap[time] = listItem.num;
          });
        }
        // 根据 X 轴数据生成完整的数据数组,没有数据的月份填充 0
        const seriesData = this.chartData.xAxisData.map((time) => {
          return timeNumMap[time] !== undefined ? timeNumMap[time] : 0;
        });
        return {
          name: name,
          name: item.name,
          type: 'line',
          smooth: false,
          smooth: true, // 平滑曲线
          symbol: 'circle',
          symbolSize: 6,
          itemStyle: {
            color: color
            borderWidth: 0, // 移除白色边框
          },
          lineStyle: {
            width: 2,
            color: color
            width: 3,
          },
          data: this.chartData.xAxisData.map(() => Math.floor(Math.random() * 100))
          emphasis: {
            focus: 'series',
            itemStyle: {
              borderWidth: 0,
              shadowBlur: 10,
            },
          },
          data: seriesData,
        };
      });
@@ -160,6 +241,8 @@
      const option = {
        backgroundColor: 'transparent',
        // 使用 color 配置项来统一设置颜色,这样 legend 和折线颜色会自动一致
        color: this.colorPalette,
        legend: {
          top: 20,
          left: 'center',
@@ -167,37 +250,73 @@
            color: '#fff',
            fontSize: 14
          },
          itemWidth: 25,
          itemHeight: 2,
          itemWidth: 15,
          itemHeight: 3,
          itemGap: 20,
          icon: 'rect'
          icon: 'rect', // 使用矩形(短横线)图标
          type: 'scroll', // 图例过多时可滚动
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          top: '80',
          bottom: '5%',
          top: '100',
          containLabel: true
        },
        tooltip: {
          trigger: 'axis',
          backgroundColor: 'rgba(0, 97, 255, 1)',
          backgroundColor: 'rgba(0, 97, 255, 0.95)',
          borderColor: '#3d7fff',
          borderWidth: 1,
          padding: [5, 5],
          padding: [10, 15],
          textStyle: {
            color: '#fff',
            fontSize: 14,
            fontSize: 13,
            align: 'left'
          },
          confine: true, // 限制在图表区域内
          axisPointer: {
            type: 'cross',
            crossStyle: {
              color: '#3d7fff'
            },
            lineStyle: {
              color: '#3d7fff',
              type: 'dashed'
            }
          },
          formatter: (params) => {
            let result = `<div style="padding: 5px 0; color: rgba(0, 235, 252, 1); font-weight: bold; font-size: 15px;">${params[0].axisValue}</div>`;
            params.forEach(item => {
              result += `<div style="display: flex; align-items: center; padding: 4px 0;margin-right: 20px;">
                <span style="display: inline-block; width: 10px; height: 10px; background-color: ${item.color}; border-radius: 50%; margin-right: 10px; flex-shrink: 0;"></span>
                <span style="flex: 1; text-align: left;">${item.seriesName}:${item.value}</span>
              </div>`;
            });
            if (!params || params.length === 0) return '';
            // 只显示有数据的项(值不为0)
            const validParams = params.filter(item => item.value !== 0);
            // 如果没有有效数据,显示所有数据
            const displayParams = validParams.length > 0 ? validParams : params;
            // 标题
            let result = `<div style="padding: 5px 0 8px 0; color: rgba(0, 235, 252, 1); font-weight: bold; font-size: 15px; border-bottom: 1px solid rgba(61, 127, 255, 0.5); margin-bottom: 8px;">${params[0].axisValue}</div>`;
            // 如果数据项超过10个,使用多列布局
            if (displayParams.length > 10) {
              result += `<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px 15px; max-width: 800px;">`;
              displayParams.forEach(item => {
                result += `<div style="display: flex; align-items: center; white-space: nowrap;">
                  <span style="display: inline-block; width: 8px; height: 8px; background-color: ${item.color}; border-radius: 50%; margin-right: 8px; flex-shrink: 0;"></span>
                  <span style="font-size: 12px;">${item.seriesName}:${item.value}</span>
                </div>`;
              });
              result += `</div>`;
            } else {
              // 数据项少时使用单列布局
              displayParams.forEach(item => {
                result += `<div style="display: flex; align-items: center; padding: 3px 0;">
                  <span style="display: inline-block; width: 10px; height: 10px; background-color: ${item.color}; border-radius: 50%; margin-right: 10px; flex-shrink: 0;"></span>
                  <span style="white-space: nowrap;">${item.seriesName}:${item.value}</span>
                </div>`;
              });
            }
            return result;
          }
        },
@@ -207,14 +326,16 @@
          data: this.chartData.xAxisData,
          axisLine: {
            lineStyle: {
              color: '#3d7fff'
              color: '#3d7fff',
              width: 2
            }
          },
          axisLabel: {
            color: '#fff',
            fontSize: 12,
            interval: 0,
            rotate: 0
            interval: 'auto',
            rotate: this.chartData.xAxisData.length > 12 ? 45 : 0, // 数据多时倾斜显示
            margin: 15
          },
          axisTick: {
            show: false
@@ -222,10 +343,17 @@
        },
        yAxis: {
          type: 'value',
          name: '数量',
          nameTextStyle: {
            color: '#fff',
            fontSize: 12,
            padding: [0, 0, 0, 0]
          },
          axisLine: {
            show: true,
            lineStyle: {
              color: '#3d7fff'
              color: '#3d7fff',
              width: 2
            }
          },
          axisLabel: {
@@ -237,12 +365,13 @@
              color: 'rgba(61, 127, 255, 0.2)',
              type: 'dashed'
            }
          }
          },
          minInterval: 1 // 确保Y轴刻度为整数
        },
        series: this.chartData.series
        series: this.chartData.series.length > 0 ? this.chartData.series : []
      };
      this.chartInstance.setOption(option);
      this.chartInstance.setOption(option, true); // true 表示不合并,完全替换
    },
    // 窗口大小改变时重新渲染
    handleResize() {
@@ -272,6 +401,10 @@
        const end = moment().year(lastYear).month(11).startOf("month").toDate();
        this.monthrange = [start, end];
      }
      // 切换标签后自动获取数据
      this.$nextTick(() => {
        this.fetchChartData();
      });
    },
    // 返回上一页
    backFun() {