hejianhao
2026-01-21 666ec84707f7e6c111b37c2d26745d8a649548fa
Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/smartBuilding
1个文件已添加
8个文件已修改
536 ■■■■■ 已修改文件
estate-frontend-v2024/config-overrides.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/src/views/dashboard/datav/components/DashboardSummary.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/src/views/dashboard/datav/components/MajorHazardList.jsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/src/views/dashboard/datav/components/ProjectTable.jsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/utils/riskSourceMap.js 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/equipmentList/index.vue 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/equipmentList/service.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/projectHome/index.vue 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/projectHome/service.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/config-overrides.js
@@ -5,6 +5,8 @@
  addWebpackAlias,
} = require("customize-cra");
const path = require("path");
const webpack = require("webpack");
function resolve(dir) {
  return path.join(__dirname, dir);
}
@@ -19,6 +21,14 @@
  if (config.resolve) {
    config.resolve.extensions.push(".jsx");
  }
  // 添加 webpack 插件来定义 process.env
  config.plugins.push(
    new webpack.DefinePlugin({
      'process.env': JSON.stringify(process.env)
    })
  );
  return config;
};
module.exports = override(
estate-frontend-v2024/src/views/dashboard/datav/components/DashboardSummary.jsx
@@ -19,7 +19,7 @@
                const isHover = hoverIndex === idx;
                return (
                    <div
                        key={item.title}
                        key={`summary-${idx}`}
                        onMouseEnter={() => setHoverIndex(idx)}
                        onMouseLeave={() => setHoverIndex(-1)}
                        style={{
estate-frontend-v2024/src/views/dashboard/datav/components/MajorHazardList.jsx
@@ -95,7 +95,7 @@
                        // 空白行
                        return (
                            <div
                                key={item.key || `blank-${idx}`}
                                key={`blank-item-${idx}`}
                                className="major-hazard-item"
                                style={{
                                    minHeight: ITEM_HEIGHT,
@@ -107,7 +107,7 @@
                    }
                    return (
                        <div
                            key={idx}
                            key={`hazard-${idx}`}
                            className="major-hazard-item"
                            style={{
                                color: '#fff',
estate-frontend-v2024/src/views/dashboard/datav/components/ProjectTable.jsx
@@ -29,9 +29,9 @@
            date: clipText(row.date, 10),
            total: (row.total !== undefined && row.total !== null && !isNaN(row.total)) ? Number(row.total).toFixed(1) : row.total,
        })),
        { key: 'blank1', name: '', client: '', plan: '', date: '', total: '' },
        { key: 'blank2', name: '', client: '', plan: '', date: '', total: '' },
        { key: 'blank3', name: '', client: '', plan: '', date: '', total: '' },
        { key: `blank-${dataSource.length + 1}`, name: '', client: '', plan: '', date: '', total: '' },
        { key: `blank-${dataSource.length + 2}`, name: '', client: '', plan: '', date: '', total: '' },
        { key: `blank-${dataSource.length + 3}`, name: '', client: '', plan: '', date: '', total: '' },
    ];
    // 跑马灯滚动效果(到底停3秒再回顶部)
screen/src/utils/riskSourceMap.js
New file
@@ -0,0 +1,97 @@
/**
 * 风险源映射配置
 * 用于将后端返回的 code 映射为对应的中文名称
 */
// 超危大工程映射
export const projectSuperDangerousTypeMap = {
  DEEP_FOUNDATION_PIT: '深基坑工程',
  TOOL_TEMPLATE: '工具式模板工程',
  TEMPLATE_SUPPORT: '模板支撑工程',
  STEEL_STRUCTURE_INSTALLATION: '钢结构安装满堂支撑体系',
  LIFTING_HOISTING: '起重吊装工程',
  CRANE_INSTALLATION_REMOVAL: '起重机械安装和拆卸工程',
  GROUND_STEEL_PIPE_SCAFFOLD: '落地式钢管脚手架工程',
  SUSPENDED_ASCENDING_SCAFFOLD: '附着式升降脚手架工程',
  CANTILEVER_SCAFFOLD: '悬挑式脚手架工程',
  DEMOLITION: '拆除工程',
  MINING_UNDERGROUND: '矿山法暗挖',
  SHIELD_TUNNELING: '盾构',
  PIPE_ROOFING: '顶管',
  BUILDING_CURTAIN_WALL: '建筑幕墙安装工程',
  STEEL_FRAME_TRUSS_TENSION: '钢结构网架和索膜结构安装工程',
  MANUAL_BORED_PILE: '人工挖孔桩工程',
  LARGE_STRUCTURE_MOVE: '大型结构整体顶升平移转体',
  UNDERWATER_OPERATION: '水下作业工程',
  FOUR_NEW_TECHNOLOGIES: '采用四新技术'
}
// 危大工程映射
export const projectDangerousTypeMap = {
  FOUNDATION_PIT: '基坑工程',
  FORMWORK_SUPPORT: '模板支撑工程',
  STEEL_STRUCTURE_INSTALLATION_SUPPORT_SYSTEM: '钢结构安装满堂支撑体系',
  HOISTING_AND_LIFTING: '起重吊装工程',
  CRANE_INSTALLATION: '起重机械进行安装的工程',
  CRANE_INSTALLATION_AND_DISMANTLING: '起重机械安装和拆卸工程',
  GROUND_PIPE_SCAFFOLD: '落地式钢管脚手架工程',
  SUSPENDED_SCAFFOLD: '附着式升降脚手架工程',
  CANTILEVER_SCAFFOLD: '悬挑式脚手架工程',
  HIGH_ALTITUDE_OPERATION_GONDOLA: '高处作业吊篮',
  UNLOADING_PLATFORM_OPERATION_PLATFORM: '卸料平台操作平台工程',
  IRREGULAR_SCAFFOLD: '异型脚手架工程',
  DEMOLITION: '拆除工程',
  BUILDING_CURTAIN_WALL_INSTALLATION: '建筑幕墙安装工程',
  STEEL_STRUCTURE_GRID_AND_MEMBRANE_STRUCTURE_INSTALLATION: '钢结构网架和索膜结构安装工程',
  MANUAL_BORED_PILE: '人工挖孔桩工程',
  PREFABRICATED_COMPONENT_INSTALLATION: '装配式建筑混凝土预制构件安装工程'
}
// 风险源映射
export const projectHazardSourceMap = {
  HIGH_ALTITUDE_OPERATION: '高处作业',
  FIRE_OPERATION: '动火作业',
  MASS_CONCRETE_POURING: '大体积混凝土浇筑',
  CONFINED_SPACE_OPERATION: '有限空间作业',
  ROAD_OCCUPATION_CONSTRUCTION: '占道施工',
  SINGLE_PERSON_OPERATION: '单人操作及作业',
  SPACE_CROSS_OPERATION: '空间交叉作业'
}
/**
 * 根据类型和 code 获取对应的中文名称
 * @param {string} type - 类型:'superDangerous' | 'dangerous' | 'hazardSource'
 * @param {string} code - 风险源代码
 * @returns {string} 中文名称,如果找不到则返回原 code
 */
export const getRiskSourceName = (type, code) => {
  const maps = {
    superDangerous: projectSuperDangerousTypeMap,
    dangerous: projectDangerousTypeMap,
    hazardSource: projectHazardSourceMap
  }
  const map = maps[type]
  return map ? (map[code] || code) : code
}
/**
 * 批量转换风险源数据
 * @param {Array} dataArray - 风险源数据数组
 * @param {string} type - 类型:'superDangerous' | 'dangerous' | 'hazardSource'
 * @param {string} riskScale - 风险规模名称
 * @returns {Array} 转换后的数据数组
 */
export const transformRiskSourceData = (dataArray, type, riskScale) => {
  if (!Array.isArray(dataArray)) return []
  return dataArray.map(item => {
    const content = getRiskSourceName(type, item)
    return {
      riskScale,
      content,
      status: '存在'
    }
  })
}
screen/src/view/equipmentList/index.vue
@@ -17,14 +17,14 @@
          <img src="../../assets/img/down-circle@2x.png" class="w--15 h--16 shrink0" />
        </div>
        <div class="flex a-center j-between pl--18 pr--15 h--40 relative w--300 bgcolor1 searchBox">
          <el-select filterable v-model="type" placeholder="请选择设备类型" class="flex1">
          <el-select filterable v-model="type" placeholder="请选择设备类型" class="flex1" @change="handleSearch" clearable>
            <el-option v-for="item in deviceTypeList" :key="item.code" :label="item.name" :value="item.code">
            </el-option>
          </el-select>
          <img src="../../assets/img/down-circle@2x.png" class="w--15 h--16 shrink0" />
        </div>
        <div @click="handleClick(1)" class="flex a-center j-between px--20 w--300 h--40 relative bgcolor1 picker">
          <el-date-picker v-model="dateTime" ref="datetime1" type="datetime" placeholder="">
          <el-date-picker v-model="dateTime" ref="datetime1" type="datetime" placeholder="" @change="handleSearch" clearable>
          </el-date-picker>
          <div class="absolute w100 h100 flex a-center c-fff fs--14 SHS" style="width: calc(100% - 20px)"
            v-if="startTime">
@@ -36,7 +36,7 @@
          <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="dateTime2" ref="datetime2" type="datetime" placeholder="">
          <el-date-picker v-model="dateTime2" ref="datetime2" type="datetime" placeholder="" @change="handleSearch" clearable>
          </el-date-picker>
          <div class="absolute w100 h100 flex a-center c-fff fs--14 SHS" style="width: calc(100% - 20px)"
            v-if="endTime">
@@ -48,31 +48,79 @@
          <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-input v-model="content" placeholder="请输入搜索内容" class="flex1"></el-input>
          <div class="btn SHSR">搜索</div>
          <el-input v-model="content" placeholder="请输入搜索内容" class="flex1" @keyup.enter="handleSearch"></el-input>
          <div class="btn SHSR" @click="handleSearch">搜索</div>
        </div>
      </div>
      <!-- 表格 -->
      <CustomTable :data="tableList" height="800"
        style="border: 2px solid #3d7fff">
        <el-table-column v-if="!projectId" label="项目名称" prop="projectName" />
        <el-table-column label="设备类型" prop="type" />
        <el-table-column label="设备类型" prop="type">
          <template #default="{ row }">
            <div>{{ getDeviceTypeName(row.type) }}</div>
          </template>
        </el-table-column>
        <el-table-column label="设备编号" prop="name" />
        <el-table-column label="设备出厂日期" prop="createTime" />
        <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="createTime">
          <template #default="{ row }">
            <div>{{ getPropertyValue(row, '设备出厂日期') || row.createTime || '-' }}</div>
          </template>
        </el-table-column>
        <el-table-column label="使用单位" prop="projectName">
          <template #default="{ row }">
            <div class="flex flex-column gap--4">
              <div v-for="(item, index) in getPropertyValues(row, '使用单位')" :key="index">
                {{ item }}
              </div>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="工程起重机械生产单位">
          <template #default="{ row }">
            <div class="flex flex-column gap--4">
              <div v-for="(item, index) in getPropertyValues(row, '工程起重机械生产单位')" :key="index">
                {{ item }}
              </div>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="工程起重机械租赁单位">
          <template #default="{ row }">
            <div class="flex flex-column gap--4">
              <div v-for="(item, index) in getPropertyValues(row, '工程起重机械租赁单位')" :key="index">
                {{ item }}
              </div>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="工程起重机械维保单位">
          <template #default="{ row }">
            <div class="flex flex-column gap--4">
              <div v-for="(item, index) in getPropertyValues(row, '工程起重机械维保单位')" :key="index">
                {{ item }}
              </div>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="工程起重机械安拆单位">
          <template #default="{ row }">
            <div class="flex flex-column gap--4">
              <div v-for="(item, index) in getPropertyValues(row, '工程起重机械安拆单位')" :key="index">
                {{ item }}
              </div>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="本月检查情况" prop="checkResult">
          <template #default="{ row, $index }">
          <template #default="{ row }">
            <div class="flex a-center j-center">
              <div class="ripple">
                <div class="ripple-wave wave-1" :class="{ bgColor3: $index === 0 }"></div>
                <div class="ripple-wave wave-2" :class="{ bgColor4: $index === 0 }"></div>
                <div class="ripple-wave wave-3" :class="{ bgColor5: $index === 0 }"></div>
                <div class="ripple-wave wave-1" :class="{ bgColor3: row.checkResult === '已检查' }"></div>
                <div class="ripple-wave wave-2" :class="{ bgColor4: row.checkResult === '已检查' }"></div>
                <div class="ripple-wave wave-3" :class="{ bgColor5: row.checkResult === '已检查' }"></div>
              </div>
              <div class="ml--6 SHSR">{{ $index }}</div>
              <div class="ml--6 SHSR">{{ row.checkResult }}</div>
            </div>
          </template>
        </el-table-column>
@@ -85,7 +133,7 @@
        </el-table-column>
      </CustomTable>
      <CustomPagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
        :current-page="currentPage" :page-sizes="[100, 200, 300, 400]" :page-size="10" :total="total" />
        :current-page="currentPage" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="total" />
    </div>
    <!-- 项目列表弹窗 -->
    <CustomDialog title="项目" :visible.sync="projectVisible" width="1420px">
@@ -126,7 +174,7 @@
<script>
import HeaderBox from "../header/index.vue";
import moment from "moment";
import { deviceTypes, getDeviceList } from './service.js'
import { deviceTypes, getDeviceList,getDeviceDetailCheckList } from './service.js'
export default {
  components: { HeaderBox },
  props: {},
@@ -161,6 +209,8 @@
      handler(newVal, oldVal) {
        if (newVal) {
          this.startTime = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
        } else {
          this.startTime = '';
        }
      },
      immediate: true,
@@ -169,6 +219,8 @@
      handler(newVal, oldVal) {
        if (newVal) {
          this.endTime = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
        } else {
          this.endTime = '';
        }
      },
      immediate: true,
@@ -191,6 +243,19 @@
      deviceTypes().then(res => {
        this.deviceTypeList = res
      })
    },
    // 根据设备类型下标获取设备类型名称
    getDeviceTypeName(typeIndex) {
      if (typeIndex === null || typeIndex === undefined || typeIndex === '') {
        return '-'
      }
      // 如果 typeIndex 是数字下标,从设备类型列表中获取对应的名称
      if (typeof typeIndex === 'number' && this.deviceTypeList[typeIndex]) {
        return this.deviceTypeList[typeIndex].name
      }
      // 如果 typeIndex 是 code,从设备类型列表中查找对应的名称
      const deviceType = this.deviceTypeList.find(item => item.code === typeIndex)
      return deviceType ? deviceType.name : typeIndex
    },
    toHiddenDanger(row, type) {
      this.$router.push({
@@ -215,17 +280,68 @@
      }
      getDeviceList({ ...params }).then(res => {
        console.log('设备列表', res)
        this.tableList = res.records
        // 处理 properties 字段
        this.tableList = res.records.map(item => {
          return {
            ...item,
            parsedProperties: this.parseProperties(item.properties)
          }
        })
        this.total = res.total
      })
    },
    // 解析 properties 字段
    parseProperties(properties) {
      if (!properties) return []
      try {
        // 如果是字符串,先解析为数组
        const propertiesArray = typeof properties === 'string' ? JSON.parse(properties) : properties
        // 确保返回的是数组
        return Array.isArray(propertiesArray) ? propertiesArray : [propertiesArray]
      } catch (error) {
        console.error('解析 properties 失败:', error)
        return []
      }
    },
    // 获取 properties 中某个字段的所有值(数组形式)
    getPropertyValues(row, fieldName) {
      if (!row.parsedProperties || row.parsedProperties.length === 0) {
        return ['-']
      }
      // 从所有 properties 对象中提取指定字段的值
      const values = row.parsedProperties
        .map(prop => prop[fieldName])
        .filter(val => val && val.trim() !== '') // 过滤空值
      return values.length > 0 ? values : ['-']
    },
    // 获取 properties 中某个字段的第一个值(单个值)
    getPropertyValue(row, fieldName) {
      if (!row.parsedProperties || row.parsedProperties.length === 0) {
        return '-'
      }
      // 从第一个 properties 对象中获取指定字段的值
      const value = row.parsedProperties[0][fieldName]
      return value && value.trim() !== '' ? value : '-'
    },
    handleSizeChange(val) {
      console.log(`每页 ${val} 条`);
      this.pageSize = val;
      this.currentPage = 1; // 改变每页条数时重置到第一页
      this.getList();
    },
    handleCurrentChange(val) {
      console.log(`当前页: ${val}`);
      this.currentPage = val;
      this.getList();
    },
    // 搜索方法
    handleSearch() {
      this.currentPage = 1; // 搜索时重置到第一页
      this.getList();
    },
    handleClick(type) {
screen/src/view/equipmentList/service.js
@@ -13,3 +13,10 @@
    data
  })
}
// 获取设备详情检查列表
export const getDeviceDetailCheckList = async (data) => {
  return request(`/project/getDeviceDetailCheckList`, {
    method: 'post',
    data
  })
}
screen/src/view/projectHome/index.vue
@@ -118,35 +118,43 @@
            <div class="danger-info">
              <div class="info-header">
                <span>隐患类型</span>
                <div class="header-line">
                <div class="header-line" @click="changeDangerSort(1)">
                  <span>隐患数量</span>
                  <img src="../../assets/img/project/up.png" />
                  <img v-if="dangerSortType === 1 && dangerSortDesc" src="../../assets/img/project/up.png" />
                  <img v-else-if="dangerSortType === 1 && !dangerSortDesc" src="../../assets/img/project/down.png" />
                  <img v-else src="../../assets/img/project/down.png" style="opacity: 0.3;" />
                </div>
                <div class="header-line">
                <div class="header-line" @click="changeDangerSort(2)">
                  <span>趋势</span>
                  <img src="../../assets/img/project/down.png" />
                  <img v-if="dangerSortType === 2 && dangerSortDesc" src="../../assets/img/project/up.png" />
                  <img v-else-if="dangerSortType === 2 && !dangerSortDesc" src="../../assets/img/project/down.png" />
                  <img v-else src="../../assets/img/project/down.png" style="opacity: 0.3;" />
                </div>
              </div>
              <div class="info-body">
                <div class="info-item" v-for="item in 12" :key="item">
                  <div class="item-type">文明施工</div>
                <div class="info-item" v-for="(item, index) in dangerTrendList" :key="index">
                  <div class="item-type">{{ item.name || '-' }}</div>
                  <div class="item-number">
                    <div class="flex a--center">
                      <div class="flex a--center">
                        <span>本月</span>
                        <div class="number">25</div>
                        <div class="number">{{ item.thisMonth || 0 }}</div>
                      </div>
                      <div class="now"></div>
                      <div class="now" :style="{ width: getBarWidth(item.thisMonth, item.lastMonth) }"></div>
                    </div>
                    <div class="flex a--center">
                      <div class="flex a--center">
                        <span>上月</span>
                        <div class="number">25</div>
                        <div class="number">{{ item.lastMonth || 0 }}</div>
                      </div>
                      <div class="before"></div>
                      <div class="before" :style="{ width: getBarWidth(item.lastMonth, item.thisMonth) }"></div>
                    </div>
                  </div>
                  <div class="item-trend">50%</div>
                  <div class="item-trend" :class="{ 'down': item.rate < 0 }">
                    <i v-if="item.rate > 0" class="trend-arrow trend-arrow-up"></i>
                    <i v-else-if="item.rate < 0" class="trend-arrow trend-arrow-down"></i>
                    <span>{{ Math.abs(item.rate) }}%</span>
                  </div>
                </div>
              </div>
@@ -199,62 +207,62 @@
          <template #content>
            <div class="people-info">
              <div class="people-card">
                <div class="people-item">
                <div class="people-item" v-if="peopleList.length > 0">
                  <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">25.4</div>
                      <div class="progress-num">{{ peopleList[0].rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">项目经理</div>
                  <div class="people-name">{{ peopleList[0].postName }}</div>
                </div>
                <div class="people-item">
                <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">25.4</div>
                      <div class="progress-num">{{ peopleList[1].rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">项目经理</div>
                  <div class="people-name">{{ peopleList[1].name }}</div>
                </div>
                <div class="people-item">
                <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">25.4</div>
                      <div class="progress-num">{{ peopleList[2].rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">项目经理</div>
                  <div class="people-name">{{ peopleList[1].name }}</div>
                </div>
              </div>
              <div class="people-card2">
                <div class="people-item mr--67">
                <div class="people-item mr--67" v-if="peopleList.length > 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>
                    <div class="progress-card">
                      <div class="progress-num">25.4</div>
                      <div class="progress-num">{{ peopleList[3].rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">项目经理</div>
                  <div class="people-name">{{ peopleList[1].name }}</div>
                </div>
                <div class="people-item">
                <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">25.4</div>
                      <div class="progress-num">{{ peopleList[4].rate }}</div>
                      <div class="progress-unit">%</div>
                    </div>
                  </div>
                  <div class="people-name">项目经理</div>
                  <div class="people-name">{{ peopleList[1].name }}</div>
                </div>
              </div>
            </div>
@@ -295,9 +303,7 @@
                  <div class="label flex1">{{ item.content }}</div>
                  <div class="label card4">{{ item.status }}</div>
                </div>
              </div>
            </div>
          </template>
@@ -334,7 +340,19 @@
<script>
import HeaderBox from "../header/index.vue";
import { getLoginInfo, getProjectDetail, getProjectDeviceCount, getDangerCount, deviceTypes } from './service.js'
import {
  getLoginInfo,
  getProjectDetail,
  getProjectDeviceCount,
  getDangerCount,
  deviceTypes,
  getProjectPersonInfo,
  getProjectSource,
  getDangerTrend
} from './service.js'
import {
  transformRiskSourceData
} from '@/utils/riskSourceMap.js'
import moment from "moment";
import * as echarts from "echarts";
export default {
@@ -354,11 +372,14 @@
      dangerCount: {},//危险源统计
      deviceTypeList: [],//设备类型列表
      equipment: 0,//设备数量
      peopleList: [],//人员列表
      // pie数据
      legendData: [],
      dateTxt: '',
      riskTrandList: [],
      dangerTrendList: [], //分项隐患趋势数据
      dangerSortType: 1, // 排序类型:1-隐患数量,2-趋势
      dangerSortDesc: true, // 是否倒序
      // 风险源数据
      riskSourceData: [
        { riskScale: '一般', content: '项目风险源内容1', status: '未开始' },
@@ -439,6 +460,7 @@
        this.projectName = res.name
      })
      this.getDetail()
      this.getDangerTrendList()
    }
  },
  mounted() {
@@ -487,41 +509,120 @@
        this.dangerCount = res
        console.log('getDangerCount getDangerCount', res)
      })
      // 项目人员信息
      getProjectPersonInfo({ projectId: this.projectId }).then(res => {
        this.peopleList = res
      })
      // 获取项目风险源
      getProjectSource({ projectId: this.projectId }).then(res => {
        const riskSourceList = []
        // 处理超危大工程
        if (res.superDangerousOptions) {
          try {
            const superDangerousArray = JSON.parse(res.superDangerousOptions)
            const transformedData = transformRiskSourceData(superDangerousArray, 'superDangerous', '超危大工程')
            riskSourceList.push(...transformedData)
          } catch (e) {
            console.error('解析超危大工程数据失败:', e)
          }
        }
        // 处理危大工程
        if (res.dangerousOptions) {
          try {
            const dangerousArray = JSON.parse(res.dangerousOptions)
            const transformedData = transformRiskSourceData(dangerousArray, 'dangerous', '危大工程')
            riskSourceList.push(...transformedData)
          } catch (e) {
            console.error('解析危大工程数据失败:', e)
          }
        }
        // 处理风险源
        if (res.hazardSource) {
          try {
            const hazardSourceArray = JSON.parse(res.hazardSource)
            const transformedData = transformRiskSourceData(hazardSourceArray, 'hazardSource', '风险源')
            riskSourceList.push(...transformedData)
          } catch (e) {
            console.error('解析风险源数据失败:', e)
          }
        }
        this.riskSourceData = riskSourceList
      })
    },
    // 获取隐患趋势
    getDangerTrendList() {
      let params = {
        projectId: this.projectId,
        type: this.dangerSortType,// 1数量 2 趋势
        desc: this.dangerSortDesc, // 是否倒序
      }
      getDangerTrend(params).then(res => {
        this.dangerTrendList = res || []
      })
    },
    // 切换隐患排序方式
    changeDangerSort(type) {
      if (this.dangerSortType === type) {
        // 如果点击的是当前排序类型,则切换升序/降序
        this.dangerSortDesc = !this.dangerSortDesc
      } else {
        // 如果点击的是不同的排序类型,则切换类型并默认倒序
        this.dangerSortType = type
        this.dangerSortDesc = true
      }
      // 重新请求数据
      this.getDangerTrendList()
    },
    // 计算进度条宽度
    getBarWidth(current, compare) {
      const max = Math.max(current, compare)
      if (max === 0) return '0px'
      const percentage = (current / max) * 100
      const maxWidth = 114 // 最大宽度
      return `${(percentage / 100) * maxWidth}px`
    },
    // 人员在岗率
    toPersonnelOnDuty() {
      this.$router.push({
        path: "/personnelOnDuty",
        query: {
          projectId: 111,
          projectName: '项目1',
          projectId: this.projectId,
          projectName: this.projectName,
        },
      });
    },
    // 跳转隐患列表
    toHiddenDanger(type) {
      this.$router.push({
        path: "/hiddenDanger",
        query: {
          projectId: 111,
          projectName: '项目1',
          projectId: this.projectId,
          projectName: this.projectName,
          activeTab: type,
        },
      });
    },
    // 跳转设备列表
    toEquipmentInfo() {
      this.$router.push({
        path: "/equipmentList",
        query: {
          projectId: 111,
          projectName: '项目1',
          projectId: this.projectId,
          projectName: this.projectName,
        },
      });
    },
    // 跳转重大危险源
    toMajorRiskList() {
      this.$router.push({
        path: "/majorRisk",
        query: {
          projectId: 111,
          projectName: '项目1',
          projectId: this.projectId,
          projectName: this.projectName,
        },
      });
    },
@@ -529,8 +630,8 @@
      this.$router.push({
        path: "/planOfAudit",
        query: {
          projectId: 111,
          projectName: '项目1',
          projectId: this.projectId,
          projectName: this.projectName,
        },
      });
    },
@@ -1334,6 +1435,12 @@
      .header-line {
        display: flex;
        align-items: center;
        cursor: pointer;
        transition: opacity 0.3s;
        &:hover {
          opacity: 0.8;
        }
        img {
          width: 7px;
@@ -1415,16 +1522,36 @@
      .item-trend {
        width: 53px;
        text-align: right;
        display: flex;
        align-items: center;
        justify-content: flex-end;
        font-weight: 800;
        font-size: 12px;
        color: #66ffff;
        line-height: 14px;
        font-style: normal;
      }
      .down {
        color: rgba(253, 66, 62, 1);
        .trend-arrow {
          width: 0;
          height: 0;
          border-left: 4px solid transparent;
          border-right: 4px solid transparent;
          margin-right: 4px;
          display: inline-block;
        }
        .trend-arrow-up {
          border-bottom: 6px solid #66ffff;
        }
        .trend-arrow-down {
          border-top: 6px solid rgba(253, 66, 62, 1);
        }
        &.down {
          color: rgba(253, 66, 62, 1);
        }
      }
    }
@@ -1693,6 +1820,29 @@
    }
    .risk-body {
      max-height: 180px; // 5行数据的高度:36px * 5 = 180px
      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);
        }
      }
      .risk-item {
        display: flex;
        align-items: stretch;
screen/src/view/projectHome/service.js
@@ -24,10 +24,28 @@
  return request(`/project/getProjectDeviceCount/${id}`, {
    method: 'get'
  })
}// 各分项隐患趋势
export const getDangerTrend = async (params) => {
  return request(`/project/getProjectHiddenDangerTrend`, {
    method: 'get', params
  })
}
// 获取项目隐患数量
export const getDangerCount = async (id) => {
  return request(`/project/getProjectHiddenDangerCount/${id}`, {
    method: 'get'
  })
}
// 获取大屏人员在岗信息
export const getProjectPersonInfo = async (params) => {
  return request(`/project/getProjectPersonInfo`, {
    method: 'get', params
  })
}
// 获取项目大屏最新重大风险源
export const getProjectSource = async (params) => {
  return request(`/project/getProjectDangerousSource`, {
    method: 'get', params
  })
}