董国庆
2026-04-01 cd19aaeb206b6358da52e8b62d025ae0e8da6416
fix: 修复ElementUI组件popperAppendToBody默认值并统一代码风格

修复ElementUI组件中popperAppendToBody和appendToBody属性的默认值设置为false,避免弹窗位置异常
调整hiddenDanger组件代码格式,统一使用单引号和一致的缩进风格
修正projectList组件中数组缺少逗号的问题
移除login组件中多余的空行
更新autofit初始化参数名从renderDom改为el
6个文件已修改
2043 ■■■■■ 已修改文件
screen/src/App.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/main.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/hiddenDanger/index.vue 536 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/login/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/projectHome/index.vue 1490 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/view/projectList/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
screen/src/App.vue
@@ -17,7 +17,7 @@
    autofit.init({
      dh: 1080,
      dw: 1920,
      renderDom: '#app',
      el: '#app',
      resize: true,
      ignore: ['#container']
    })
screen/src/main.js
@@ -45,6 +45,18 @@
  return moment(dataStr).format(pattern)
})
Object.keys(ElementUI).forEach((key) => {
  const comp = ElementUI[key]
  if (comp && comp.props) {
    if (comp.props.popperAppendToBody) {
      comp.props.popperAppendToBody.default = false
    }
    if (comp.props.appendToBody) {
      comp.props.appendToBody.default = false
    }
  }
})
Vue.use(ElementUI)
Vue.use(animated)
screen/src/view/hiddenDanger/index.vue
@@ -645,27 +645,27 @@
</template>
<script>
import HeaderBox from "../header/index.vue";
import moment from "moment";
import HeaderBox from '../header/index.vue'
import moment from 'moment'
import {
  getProjectList,
  getHiddenDangerListByType,
  getHiddenDangerDetailByType,
  editResponsiblePersonByType,
} from "./service";
import { errorLevelMap, errorReviewStateMap } from "@/utils/riskSourceMap";
  editResponsiblePersonByType
} from './service'
import { errorLevelMap, errorReviewStateMap } from '@/utils/riskSourceMap'
export default {
  components: { HeaderBox },
  props: {},
  data() {
  data () {
    return {
      // Tab 相关
      activeTab: 1, // 1=重大隐患 2=安全隐患 3=设备隐患
      // 路由参数
      projectId: null, // 项目ID,如果有则是项目端使用
      projectName: "", // 项目名称
      projectName: '', // 项目名称
      isClient: false, // 是否委托方端
      routeType: null,
@@ -677,14 +677,14 @@
        level: null, // 隐患等级:0=一级 1=二级 2=三级 3=四级
        startTime: null, // 开始时间
        endTime: null, // 结束时间
        content: "", // 搜索内容
        content: '' // 搜索内容
      },
      // 日期选择
      startDateTime: null,
      endDateTime: null,
      startDateTxt: "",
      endDateTxt: "",
      startDateTxt: '',
      endDateTxt: '',
      // 保存从路由获取的时间参数,用于切换tab时保留
      routeStartTime: null,
@@ -704,12 +704,12 @@
      // 弹窗相关
      updatePeople: false,
      peopleName: "",
      peopleName: '',
      currentEditRow: null, // 当前编辑的行数据
      detailVisible: false,
      detailData: null, // 详情数据
      currentRow: null, // 当前查看详情的列表行数据
      activeName: "first",
      activeName: 'first',
      previewVisible: false,
      previewImages: [], // 预览图片列表
@@ -718,212 +718,212 @@
      generalHiddenDangerConfig: null, // 一般隐患配置:1开启 2关闭
      projectCategory: null,
      projectType: sessionStorage.getItem("projectType"), //1委托方 2项目方
    };
      projectType: sessionStorage.getItem('projectType') // 1委托方 2项目方
    }
  },
  computed: {
    // 是否是项目端(有projectId参数)
    isProjectMode() {
      return this.routeType === 2;
    isProjectMode () {
      return this.routeType === 2
    },
    // 是否是委托方端(没有projectId参数)
    isClientMode() {
      return this.routeType !== 2;
    isClientMode () {
      return this.routeType !== 2
    },
    hideProjectHeader() {
      return this.isProjectMode;
    hideProjectHeader () {
      return this.isProjectMode
    },
    hideSearchArea() {
      return this.hideProjectHeader;
    hideSearchArea () {
      return this.hideProjectHeader
    },
    // 当前隐患类型(根据activeTab判断)
    currentType() {
    currentType () {
      // 1=重大隐患(暂无对应type) 2=安全隐患(type=1) 3=设备隐患(type=2)
      if (this.activeTab === 2) return 1;
      if (this.activeTab === 3) return 2;
      return null;
      if (this.activeTab === 2) return 1
      if (this.activeTab === 3) return 2
      return null
    },
    // 是否显示隐患类型筛选(仅重大隐患显示)
    showTypeFilter() {
      return this.activeTab === 1;
    showTypeFilter () {
      return this.activeTab === 1
    },
    // 是否显示隐患等级筛选(安全和设备隐患显示)
    showLevelFilter() {
      return this.activeTab !== 1;
    showLevelFilter () {
      return this.activeTab !== 1
    },
    // 是否显示整改状态筛选
    showReformedFilter() {
    showReformedFilter () {
      // activeTab: 1=重大隐患 2=安全隐患 3=设备隐患
      // bigHiddenDangerConfig: 1开启 2关闭
      // generalHiddenDangerConfig: 1开启 2关闭
      if (this.bigHiddenDangerConfig === 2) {
        return false;
        return false
      } else if (this.generalHiddenDangerConfig === 2) {
        return this.activeTab === 1;
        return this.activeTab === 1
      } else {
        return true;
        return true
      }
    },
    }
  },
  watch: {
    // 监听 tab 切换
    activeTab: {
      handler(newVal) {
      handler (newVal) {
        // 切换 tab 时重置筛选条件并重新加载数据
        this.resetSearchForm();
        this.pageNum = 1;
        this.loadTableData();
      },
        this.resetSearchForm()
        this.pageNum = 1
        this.loadTableData()
      }
    },
    // 监听开始时间
    startDateTime: {
      handler(newVal) {
      handler (newVal) {
        if (newVal) {
          this.startDateTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
          this.startDateTxt = moment(newVal).format('YYYY年MM月DD日 HH:mm:ss')
          this.searchForm.startTime = moment(newVal).format(
            "YYYY-MM-DD HH:mm:ss"
          );
            'YYYY-MM-DD HH:mm:ss'
          )
          // 更新保存的路由时间
          this.routeStartTime = this.searchForm.startTime;
          this.routeStartTime = this.searchForm.startTime
          // 选择时间后自动请求
          this.handleSelectChange();
          this.handleSelectChange()
        } else {
          this.startDateTxt = "";
          this.searchForm.startTime = null;
          this.startDateTxt = ''
          this.searchForm.startTime = null
        }
      },
      }
    },
    // 监听结束时间
    endDateTime: {
      handler(newVal) {
      handler (newVal) {
        if (newVal) {
          this.endDateTxt = moment(newVal).format("YYYY年MM月DD日 HH:mm:ss");
          this.endDateTxt = moment(newVal).format('YYYY年MM月DD日 HH:mm:ss')
          this.searchForm.endTime = moment(newVal).format(
            "YYYY-MM-DD HH:mm:ss"
          );
            'YYYY-MM-DD HH:mm:ss'
          )
          // 更新保存的路由时间
          this.routeEndTime = this.searchForm.endTime;
          this.routeEndTime = this.searchForm.endTime
          // 选择时间后自动请求
          this.handleSelectChange();
          this.handleSelectChange()
        } else {
          this.endDateTxt = "";
          this.searchForm.endTime = null;
          this.endDateTxt = ''
          this.searchForm.endTime = null
        }
      },
    },
      }
    }
  },
  created() {
  created () {
    // 从 sessionStorage 读取配置
    this.loadConfigFromsessionStorage();
    this.loadConfigFromsessionStorage()
    // 从路由获取参数
    if (this.$route.query.activeTab) {
      this.activeTab = Number(this.$route.query.activeTab);
      this.activeTab = Number(this.$route.query.activeTab)
    }
    if (this.$route.query.projectId) {
      this.projectId = this.$route.query.projectId;
      this.searchForm.projectId = this.$route.query.projectId;
      this.projectId = this.$route.query.projectId
      this.searchForm.projectId = this.$route.query.projectId
    }
    if (this.$route.query.projectName) {
      this.projectName = this.$route.query.projectName;
      this.projectName = this.$route.query.projectName
    }
    if (this.$route.query.projectCategory) {
      this.projectCategory = this.$route.query.projectCategory;
      this.projectCategory = this.$route.query.projectCategory
    }
    if (this.$route.query.type) {
      this.routeType = Number(this.$route.query.type);
      this.routeType = Number(this.$route.query.type)
    }
    // 接收从 home 页面传递过来的时间参数并回填
    if (this.$route.query.startTime) {
      this.routeStartTime = this.$route.query.startTime;
      this.startDateTime = new Date(this.$route.query.startTime);
      this.searchForm.startTime = this.$route.query.startTime;
      this.routeStartTime = this.$route.query.startTime
      this.startDateTime = new Date(this.$route.query.startTime)
      this.searchForm.startTime = this.$route.query.startTime
    }
    if (this.$route.query.endTime) {
      this.routeEndTime = this.$route.query.endTime;
      this.endDateTime = new Date(this.$route.query.endTime);
      this.searchForm.endTime = this.$route.query.endTime;
      this.routeEndTime = this.$route.query.endTime
      this.endDateTime = new Date(this.$route.query.endTime)
      this.searchForm.endTime = this.$route.query.endTime
    }
    // 判断是否委托方端:通过 type 判断(type==2 为项目端)
    this.isClient = this.routeType !== 2;
    this.isClient = this.routeType !== 2
    // 初始化下拉选项
    this.initOptions();
    this.initOptions()
    // 加载项目列表(如果不是项目端)
    // if (!this.isProjectMode) { isProjectMode
    this.loadProjectList();
    this.loadProjectList()
    // }
    // 加载表格数据
    this.loadTableData();
    this.loadTableData()
  },
  mounted() {},
  mounted () {},
  methods: {
    // 从 sessionStorage 加载配置
    loadConfigFromsessionStorage() {
    loadConfigFromsessionStorage () {
      try {
        const bigHiddenDanger = sessionStorage.getItem("bigHiddenDanger");
        const bigHiddenDanger = sessionStorage.getItem('bigHiddenDanger')
        const generalHiddenDanger = sessionStorage.getItem(
          "generalHiddenDanger"
        );
          'generalHiddenDanger'
        )
        // 转换为数字类型,默认为开启(1)
        this.bigHiddenDangerConfig = bigHiddenDanger
          ? Number(bigHiddenDanger)
          : 1;
          : 1
        this.generalHiddenDangerConfig = generalHiddenDanger
          ? Number(generalHiddenDanger)
          : 1;
          : 1
      } catch (error) {
        console.error("加载配置失败:", error);
        console.error('加载配置失败:', error)
        // 默认都开启
        this.bigHiddenDangerConfig = 1;
        this.generalHiddenDangerConfig = 1;
        this.bigHiddenDangerConfig = 1
        this.generalHiddenDangerConfig = 1
      }
    },
    // 初始化下拉选项
    initOptions() {
    initOptions () {
      if (this.projectType == 1) {
        this.reformedOptions = [
          { label: "未复查", value: 0 },
          { label: "未整改", value: 1 },
          { label: "部分整改", value: 2 },
          { label: "已整改", value: 3 },
        ];
          { label: '未复查', value: 0 },
          { label: '未整改', value: 1 },
          { label: '部分整改', value: 2 },
          { label: '已整改', value: 3 }
        ]
      } else {
        // 整改状态选项
        this.reformedOptions = [
          { label: "未整改", value: 0 },
          { label: "部分整改", value: 1 },
          { label: "已整改", value: 2 },
          { label: "未复查", value: 3 },
        ];
          { label: '未整改', value: 0 },
          { label: '部分整改', value: 1 },
          { label: '已整改', value: 2 },
          { label: '未复查', value: 3 }
        ]
      }
      // 隐患等级选项
      this.levelOptions = [
        { label: "一级隐患", value: 0 },
        { label: "二级隐患", value: 1 },
        { label: "三级隐患", value: 2 },
        { label: "四级隐患", value: 3 },
      ];
        { label: '一级隐患', value: 0 },
        { label: '二级隐患', value: 1 },
        { label: '三级隐患', value: 2 },
        { label: '四级隐患', value: 3 }
      ]
    },
    // 重置搜索表单
    resetSearchForm() {
    resetSearchForm () {
      this.searchForm = {
        projectId: this.projectId || null,
        state: null,
@@ -931,210 +931,210 @@
        level: null,
        startTime: this.routeStartTime || null, // 保留路由传递的开始时间
        endTime: this.routeEndTime || null, // 保留路由传递的结束时间
        content: "",
      };
        content: ''
      }
      // 保留时间选择器的值
      if (this.routeStartTime) {
        this.startDateTime = new Date(this.routeStartTime);
        this.startDateTime = new Date(this.routeStartTime)
      } else {
        this.startDateTime = null;
        this.startDateTime = null
      }
      if (this.routeEndTime) {
        this.endDateTime = new Date(this.routeEndTime);
        this.endDateTime = new Date(this.routeEndTime)
      } else {
        this.endDateTime = null;
        this.endDateTime = null
      }
    },
    // 加载项目列表
    async loadProjectList() {
    async loadProjectList () {
      try {
        // 从 sessionStorage 获取 userInfo
        const userInfoStr = sessionStorage.getItem("userInfo");
        const userInfoStr = sessionStorage.getItem('userInfo')
        if (!userInfoStr) {
          console.error("未找到用户信息");
          return;
          console.error('未找到用户信息')
          return
        }
        const clientId = sessionStorage.getItem("clientId");
        const clientId = sessionStorage.getItem('clientId')
        if (!clientId) {
          console.error("用户信息中未找到 clientId");
          return;
          console.error('用户信息中未找到 clientId')
          return
        }
        const activeTabIndex = Number(sessionStorage.getItem("activeTab")) - 1;
        const activeTabIndex = Number(sessionStorage.getItem('activeTab')) - 1
        const res = await getProjectList({
          clientId: clientId,
          projectCategory: ["ongoing", "fixed"][activeTabIndex],
        });
          projectCategory: ['ongoing', 'fixed'][activeTabIndex]
        })
        if (res && res) {
          this.projectList = res.map((item) => ({
            label: item.name,
            value: item.id,
          }));
            value: item.id
          }))
        }
      } catch (error) {
        console.error("加载项目列表失败:", error);
        console.error('加载项目列表失败:', error)
      }
    },
    // 加载表格数据
    async loadTableData() {
      this.loading = true;
    async loadTableData () {
      this.loading = true
      try {
        const params = {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
          category: 2,
          ...this.searchForm,
        };
          ...this.searchForm
        }
        // 项目端:使用 currentType(根据 Tab 自动判断)
        // 委托方:
        //   - 重大隐患 Tab:使用 searchForm.type(用户选择的隐患类型筛选)
        //   - 安全/设备 Tab:不需要传 type(已经通过不同接口区分)
        console.log("this.isClient", this.isClient);
        console.log('this.isClient', this.isClient)
        if (!this.isClient) {
          // 项目端:传递 type
          params.type = this.currentType || this.searchForm.type;
          params.type = this.currentType || this.searchForm.type
          if (this.activeTab === 1) {
            params.category = 1;
            params.category = 1
          }
        } else {
          params.clientId = sessionStorage.getItem("clientId");
          params.clientId = sessionStorage.getItem('clientId')
          if (this.projectCategory) {
            params.category = sessionStorage.getItem("activeTab");
            params.category = sessionStorage.getItem('activeTab')
          }
          // 委托方:只在重大隐患 Tab 且用户选择了类型时传递 type
          if (this.activeTab === 1 && this.searchForm.type) {
            params.type = this.searchForm.type;
            params.category = 1;
            params.type = this.searchForm.type
            params.category = 1
          }
        }
        // 过滤掉空值
        Object.keys(params).forEach((key) => {
          if (params[key] === null || params[key] === "") {
            delete params[key];
          if (params[key] === null || params[key] === '') {
            delete params[key]
          }
        });
        })
        // 使用统一方法,自动根据用户类型和Tab调用对应接口
        const res = await getHiddenDangerListByType(
          params,
          this.activeTab,
          this.isClient
        );
        )
        if (res) {
          this.tableData = res.records || [];
          this.total = res.total || 0;
          this.tableData = res.records || []
          this.total = res.total || 0
        }
      } catch (error) {
        console.error("加载表格数据失败:", error);
        this.$message.error("加载数据失败");
        console.error('加载表格数据失败:', error)
        this.$message.error('加载数据失败')
      } finally {
        this.loading = false;
        this.loading = false
      }
    },
    // 搜索
    handleSearch() {
      this.pageNum = 1;
      this.loadTableData();
    handleSearch () {
      this.pageNum = 1
      this.loadTableData()
    },
    // 选择器改变时自动请求
    handleSelectChange() {
      this.pageNum = 1;
      this.loadTableData();
    handleSelectChange () {
      this.pageNum = 1
      this.loadTableData()
    },
    // 清除选择器
    clearSelect(field) {
      this.searchForm[field] = null;
      this.handleSelectChange();
    clearSelect (field) {
      this.searchForm[field] = null
      this.handleSelectChange()
    },
    // 清除日期时间
    clearDateTime(type) {
      if (type === "start") {
        this.startDateTime = null;
        this.startDateTxt = "";
        this.searchForm.startTime = null;
        this.routeStartTime = null; // 清除保存的路由时间
    clearDateTime (type) {
      if (type === 'start') {
        this.startDateTime = null
        this.startDateTxt = ''
        this.searchForm.startTime = null
        this.routeStartTime = null // 清除保存的路由时间
      } else {
        this.endDateTime = null;
        this.endDateTxt = "";
        this.searchForm.endTime = null;
        this.routeEndTime = null; // 清除保存的路由时间
        this.endDateTime = null
        this.endDateTxt = ''
        this.searchForm.endTime = null
        this.routeEndTime = null // 清除保存的路由时间
      }
      this.handleSelectChange();
      this.handleSelectChange()
    },
    // 点击日期选择器
    handleClick(type) {
    handleClick (type) {
      if (type === 1) {
        this.$refs.datetime1.focus();
        this.$refs.datetime1.focus()
      } else {
        this.$refs.datetime2.focus();
        this.$refs.datetime2.focus()
      }
    },
    // 获取隐患等级文本
    getLevelText(level) {
      return this.levelOptions[level].label || "-";
    getLevelText (level) {
      return this.levelOptions[level].label || '-'
    },
    // 获取整改状态文本
    getReformedText(state) {
      return ["未整改", "部分整改", "已整改", "未复查"][state];
    getReformedText (state) {
      return ['未整改', '部分整改', '已整改', '未复查'][state]
    },
    // 预览图片
    handlePreviewImages(userFiles) {
    handlePreviewImages (userFiles) {
      if (!userFiles || userFiles.length === 0) {
        this.$message.warning("暂无图片");
        return;
        this.$message.warning('暂无图片')
        return
      }
      // this.previewImages = userFiles.map((file) => file.url || file.name);
      const baseUrl = process.env.VUE_APP_IMAGE_BASE_URL || "";
      const baseUrl = process.env.VUE_APP_IMAGE_BASE_URL || ''
      this.previewImages = userFiles.map((file) => {
        let img = file.url || file.name;
        img = img.split(".")[0] || img;
        let img = file.url || file.name
        img = img.split('.')[0] || img
        // 如果图片已经是完整URL,直接返回;否则添加前缀
        if (img.startsWith("http://") || img.startsWith("https://")) {
          return img;
        if (img.startsWith('http://') || img.startsWith('https://')) {
          return img
        }
        return baseUrl + "/project/download/" + img;
      });
      this.previewVisible = true;
        return baseUrl + '/project/download/' + img
      })
      this.previewVisible = true
    },
    // 编辑责任人
    handleEditPeople(row) {
      this.currentEditRow = row;
      this.peopleName = row.responsiblePerson || "";
      this.updatePeople = true;
    handleEditPeople (row) {
      this.currentEditRow = row
      this.peopleName = row.responsiblePerson || ''
      this.updatePeople = true
    },
    // 关闭编辑责任人弹窗
    closeUpdatePeople() {
      this.updatePeople = false;
      this.peopleName = "";
      this.currentEditRow = null;
    closeUpdatePeople () {
      this.updatePeople = false
      this.peopleName = ''
      this.currentEditRow = null
    },
    // 保存责任人
    async saveUpdatePeople() {
    async saveUpdatePeople () {
      if (!this.peopleName) {
        this.$message({
          message: "请输入责任人",
          type: "warning",
        });
        return;
          message: '请输入责任人',
          type: 'warning'
        })
        return
      }
      try {
@@ -1142,91 +1142,91 @@
        await editResponsiblePersonByType(
          {
            id: this.currentEditRow.id,
            responsiblePerson: this.peopleName,
            responsiblePerson: this.peopleName
          },
          this.isClient
        );
        )
        this.$message({
          message: "保存成功",
          type: "success",
        });
          message: '保存成功',
          type: 'success'
        })
        // 更新表格数据
        this.currentEditRow.responsiblePerson = this.peopleName;
        this.closeUpdatePeople();
        this.currentEditRow.responsiblePerson = this.peopleName
        this.closeUpdatePeople()
      } catch (error) {
        console.error("保存责任人失败:", error);
        this.$message.error("保存失败");
        console.error('保存责任人失败:', error)
        this.$message.error('保存失败')
      }
    },
    // 查看详情
    async handleViewDetail(row) {
    async handleViewDetail (row) {
      try {
        // 保存当前行数据
        this.currentRow = row;
        this.currentRow = row
        // 使用统一方法
        const res = await getHiddenDangerDetailByType(row.id, this.isClient);
        const res = await getHiddenDangerDetailByType(row.id, this.isClient)
        if (res && res) {
          this.detailData = res;
          this.detailVisible = true;
          this.activeName = "first";
          this.detailData = res
          this.detailVisible = true
          this.activeName = 'first'
        }
      } catch (error) {
        console.error("加载详情失败:", error);
        this.$message.error("加载详情失败");
        console.error('加载详情失败:', error)
        this.$message.error('加载详情失败')
      }
    },
    // 切换详情 tab
    handleClickTab(tab) {
      this.activeName = tab.name;
    handleClickTab (tab) {
      this.activeName = tab.name
    },
    // 获取复查记录标签
    getReviewRecordsLabel() {
    getReviewRecordsLabel () {
      if (!this.detailData || !this.detailData.errorReview) {
        return "复查记录(无)";
        return '复查记录(无)'
      }
      return "复查记录";
      return '复查记录'
    },
    // 获取复查状态文本
    getReviewStateText(state) {
    getReviewStateText (state) {
      const stateMap = {
        0: "未整改",
        1: "部分整改",
        2: "已整改",
        3: "未复查",
      };
      return stateMap[state] || "-";
        0: '未整改',
        1: '部分整改',
        2: '已整改',
        3: '未复查'
      }
      return stateMap[state] || '-'
    },
    // 预览复查图片(需要添加前缀)
    handlePreviewReviewImages(imgArray) {
    handlePreviewReviewImages (imgArray) {
      if (!imgArray || imgArray.length === 0) {
        this.$message.warning("暂无图片");
        return;
        this.$message.warning('暂无图片')
        return
      }
      // 图片没有前缀,需要根据实际情况添加图片服务器地址前缀
      // 假设图片服务器地址为 process.env.VUE_APP_IMG_BASE_URL 或者其他配置
      const baseUrl = process.env.VUE_APP_IMAGE_BASE_URL || "";
      const baseUrl = process.env.VUE_APP_IMAGE_BASE_URL || ''
      this.previewImages = imgArray.map((img) => {
        img = img.split(".")[0] || img;
        img = img.split('.')[0] || img
        // 如果图片已经是完整URL,直接返回;否则添加前缀
        if (img.startsWith("http://") || img.startsWith("https://")) {
          return img;
        if (img.startsWith('http://') || img.startsWith('https://')) {
          return img
        }
        return baseUrl + "/project/download/" + img;
      });
      this.previewVisible = true;
        return baseUrl + '/project/download/' + img
      })
      this.previewVisible = true
    },
    // 获取问题数量名称(取第一条数据的 name)
    getNumberFactorName() {
    getNumberFactorName () {
      if (
        this.detailData &&
        this.detailData.numberFactors &&
@@ -1234,20 +1234,20 @@
      ) {
        try {
          const firstItem =
            typeof this.detailData.numberFactors[0] === "string"
            typeof this.detailData.numberFactors[0] === 'string'
              ? JSON.parse(this.detailData.numberFactors[0])
              : this.detailData.numberFactors[0];
          return firstItem.name || "-";
              : this.detailData.numberFactors[0]
          return firstItem.name || '-'
        } catch (error) {
          console.error("解析 numberFactors 失败:", error);
          return "-";
          console.error('解析 numberFactors 失败:', error)
          return '-'
        }
      }
      return "-";
      return '-'
    },
    // 获取扣除分数(取第一条数据的 deductPoint)
    getNumberFactorDeductPoint() {
    getNumberFactorDeductPoint () {
      if (
        this.detailData &&
        this.detailData.numberFactors &&
@@ -1255,23 +1255,23 @@
      ) {
        try {
          const firstItem =
            typeof this.detailData.numberFactors[0] === "string"
            typeof this.detailData.numberFactors[0] === 'string'
              ? JSON.parse(this.detailData.numberFactors[0])
              : this.detailData.numberFactors[0];
          const deductPoint = firstItem.deductPoint;
              : this.detailData.numberFactors[0]
          const deductPoint = firstItem.deductPoint
          return deductPoint !== undefined && deductPoint !== null
            ? deductPoint
            : "-";
            : '-'
        } catch (error) {
          console.error("解析 numberFactors 失败:", error);
          return "-";
          console.error('解析 numberFactors 失败:', error)
          return '-'
        }
      }
      return "-";
      return '-'
    },
    // 获取附加加分(取第一条数据的 additionPoint)
    getNumberFactorAdditionPoint() {
    getNumberFactorAdditionPoint () {
      if (
        this.detailData &&
        this.detailData.numberFactors &&
@@ -1279,40 +1279,40 @@
      ) {
        try {
          const firstItem =
            typeof this.detailData.numberFactors[0] === "string"
            typeof this.detailData.numberFactors[0] === 'string'
              ? JSON.parse(this.detailData.numberFactors[0])
              : this.detailData.numberFactors[0];
          const additionPoint = firstItem.additionPoint;
              : this.detailData.numberFactors[0]
          const additionPoint = firstItem.additionPoint
          return additionPoint !== undefined && additionPoint !== null
            ? additionPoint
            : "-";
            : '-'
        } catch (error) {
          console.error("解析 numberFactors 失败:", error);
          return "-";
          console.error('解析 numberFactors 失败:', error)
          return '-'
        }
      }
      return "-";
      return '-'
    },
    // 返回
    backFun() {
      this.$router.go(-1);
    backFun () {
      this.$router.go(-1)
    },
    // 分页-每页条数改变
    handleSizeChange(val) {
      this.pageSize = val;
      this.pageNum = 1;
      this.loadTableData();
    handleSizeChange (val) {
      this.pageSize = val
      this.pageNum = 1
      this.loadTableData()
    },
    // 分页-当前页改变
    handleCurrentChange(val) {
      this.pageNum = val;
      this.loadTableData();
    },
  },
};
    handleCurrentChange (val) {
      this.pageNum = val
      this.loadTableData()
    }
  }
}
</script>
<style scoped lang="less">
input::placeholder {
screen/src/view/login/index.vue
@@ -297,7 +297,6 @@
              message: err,
              type: 'error'
            })
          })
      }
    },
screen/src/view/projectHome/index.vue
@@ -596,8 +596,8 @@
</template>
<script>
import HeaderBox from "../header/index.vue";
import moment from "moment";
import HeaderBox from '../header/index.vue'
import moment from 'moment'
import {
  getLoginInfo,
  getProjectDetail,
@@ -608,43 +608,43 @@
  getProjectSource,
  getDangerTrend,
  getProjectDangerousTrend,
  getProjectLatestProgress,
} from "./service.js";
import { transformRiskSourceData } from "@/utils/riskSourceMap.js";
import * as echarts from "echarts";
  getProjectLatestProgress
} from './service.js'
import { transformRiskSourceData } from '@/utils/riskSourceMap.js'
import * as echarts from 'echarts'
export default {
  components: {
    HeaderBox,
    HeaderBox
  },
  data() {
  data () {
    return {
      mapView: null,
      mapPromise: null,
      _AMap: null,
      _mapInitPromise: null,
      _mapResizeHandler: null,
      _lastMarkerPosKey: "",
      _lastMarkerPosKey: '',
      currentMarker: null, // 保存当前的 marker 实例
      dateRange: [
        moment().subtract(5, "months").startOf("month"),
        moment().endOf("month"),
        moment().subtract(5, 'months').startOf('month'),
        moment().endOf('month')
      ], // 默认近6个月
      markers: [],
      projectId: "",
      projectName: "",
      projectId: '',
      projectName: '',
      // 项目详情信息
      projectInfo: {
        addressInfo: {},
        address: "",
        address: '',
        constructionCompanyInfo: {},
        developmentCompanyInfo: {},
        supervisorCompanyInfo: {},
        properties: [],
        properties: []
      }, // 项目信息
      dangerCount: {
        serious: 0,
        rectified: 0,
        unRectified: 0,
        unRectified: 0
      }, // 危险源统计
      deviceTypeList: [], // 设备类型列表
      equipment: 0, // 设备数量
@@ -652,7 +652,7 @@
      // pie数据
      legendData: [],
      dateTxt: "",
      dateTxt: '',
      dangerTrendList: [], // 分项隐患趋势数据
      dangerSortType: 1, // 排序类型:1-隐患数量,2-趋势
      dangerSortDesc: true, // 是否倒序
@@ -661,11 +661,11 @@
      // 项目风险趋势数据
      riskTrendData: {
        xAxisData: [],
        seriesData: [],
        seriesData: []
      },
      // 9各分项隐患趋势 10隐患数量 11设备数量 12查看报告入口 13人员在岗率 14重大风险源 15 项目风险趋势
      permissionProject:
        JSON.parse(sessionStorage.getItem("permissionProject")) || [],
        JSON.parse(sessionStorage.getItem('permissionProject')) || [],
      // 项目进度相关
      progressVisible: false, // 进度弹窗显示状态
@@ -675,32 +675,32 @@
      progressMediaList: [], // 处理后的媒体列表(包含类型信息)
      // 预览相关
      previewVisible: false, // 预览弹窗显示状态
      previewUrl: "", // 预览的URL
      previewType: "image", // 预览类型:'image' 或 'video'
      clientName: "", // 客户名称
      clientLogo: "", // 客户logo
    };
      previewUrl: '', // 预览的URL
      previewType: 'image', // 预览类型:'image' 或 'video'
      clientName: '', // 客户名称
      clientLogo: '' // 客户logo
    }
  },
  computed: {
    // 处理风险源数据,添加合并单元格的逻辑
    riskSourceList() {
    riskSourceList () {
      if (!this.riskSourceData || this.riskSourceData.length === 0) {
        return [];
        return []
      }
      const result = [];
      let i = 0;
      const result = []
      let i = 0
      while (i < this.riskSourceData.length) {
        const currentRiskScale = this.riskSourceData[i].riskScale;
        let rowspan = 1;
        const currentRiskScale = this.riskSourceData[i].riskScale
        let rowspan = 1
        // 计算相同风险规模的行数
        for (let j = i + 1; j < this.riskSourceData.length; j++) {
          if (this.riskSourceData[j].riskScale === currentRiskScale) {
            rowspan++;
            rowspan++
          } else {
            break;
            break
          }
        }
@@ -708,710 +708,710 @@
        result.push({
          ...this.riskSourceData[i],
          showRiskScale: true,
          rowspan: rowspan,
        });
          rowspan: rowspan
        })
        // 添加后续相同风险规模的行,不显示风险规模
        for (let k = 1; k < rowspan; k++) {
          result.push({
            ...this.riskSourceData[i + k],
            showRiskScale: false,
            rowspan: 0,
          });
            rowspan: 0
          })
        }
        i += rowspan;
        i += rowspan
      }
      return result;
      return result
    },
    filteredProperties() {
    filteredProperties () {
      if (
        !this.projectInfo.properties ||
        this.projectInfo.properties.length === 0
      ) {
        return [];
        return []
      }
      const uniqueProperties = [];
      const seen = new Set();
      const uniqueProperties = []
      const seen = new Set()
      this.projectInfo.properties.forEach((itemStr) => {
        try {
          const item = JSON.parse(itemStr);
          const rentalUnit = item["工程起重机械租赁单位"];
          const contact = item["联系电话"];
          const item = JSON.parse(itemStr)
          const rentalUnit = item['工程起重机械租赁单位']
          const contact = item['联系电话']
          if (rentalUnit || contact) {
            const key = `${rentalUnit}-${contact}`;
            const key = `${rentalUnit}-${contact}`
            if (!seen.has(key)) {
              seen.add(key);
              uniqueProperties.push(item);
              seen.add(key)
              uniqueProperties.push(item)
            }
          }
        } catch (e) {
          console.error("Parsing project properties failed:", e);
          console.error('Parsing project properties failed:', e)
        }
      });
      console.log("uniqueProperties uniqueProperties", uniqueProperties);
      })
      console.log('uniqueProperties uniqueProperties', uniqueProperties)
      return uniqueProperties;
      return uniqueProperties
    },
    filteredRentalUnits() {
    filteredRentalUnits () {
      if (
        !this.projectInfo.properties ||
        this.projectInfo.properties.length === 0
      ) {
        return [];
        return []
      }
      const list = [];
      const seen = new Set();
      const list = []
      const seen = new Set()
      this.projectInfo.properties.forEach((itemStr) => {
        try {
          const item = JSON.parse(itemStr);
          console.log('item item',item)
          const rentalUnit = item["工程起重机械租赁单位"];
          if (!rentalUnit) return;
          const key = String(rentalUnit).trim();
          if (!key) return;
          if (seen.has(key)) return;
          seen.add(key);
          list.push({ "工程起重机械租赁单位": rentalUnit });
          const item = JSON.parse(itemStr)
          console.log('item item', item)
          const rentalUnit = item['工程起重机械租赁单位']
          if (!rentalUnit) return
          const key = String(rentalUnit).trim()
          if (!key) return
          if (seen.has(key)) return
          seen.add(key)
          list.push({ '工程起重机械租赁单位': rentalUnit })
        } catch (e) {
          return;
        }
      });
      console.log('1111111111111',list)
      return list;
        }
      })
      console.log('1111111111111', list)
      return list
    },
    filteredContacts() {
    filteredContacts () {
      if (
        !this.projectInfo.properties ||
        this.projectInfo.properties.length === 0
      ) {
        return [];
        return []
      }
      const list = [];
      const seen = new Set();
      const list = []
      const seen = new Set()
      this.projectInfo.properties.forEach((itemStr) => {
        try {
          const item = JSON.parse(itemStr);
          const contact = item["联系电话"];
          if (!contact) return;
          const key = String(contact).trim();
          if (!key) return;
          if (seen.has(key)) return;
          seen.add(key);
          list.push({ "联系电话": contact });
          const item = JSON.parse(itemStr)
          const contact = item['联系电话']
          if (!contact) return
          const key = String(contact).trim()
          if (!key) return
          if (seen.has(key)) return
          seen.add(key)
          list.push({ '联系电话': contact })
        } catch (e) {
          return;
        }
      });
      return list;
    },
        }
      })
      return list
    }
  },
  watch: {
    dateRange: {
      handler(newVal, oldVal) {
      handler (newVal, oldVal) {
        if (newVal.length > 0) {
          this.dateTxt =
            moment(newVal[0]).format("YYYY -MM") +
            " - " +
            moment(newVal[1]).format("YYYY -MM");
            moment(newVal[0]).format('YYYY -MM') +
            ' - ' +
            moment(newVal[1]).format('YYYY -MM')
          // 当日期范围改变时,重新获取风险趋势数据
          if (this.projectId) {
            this.getProjectRiskTrend();
            this.getProjectRiskTrend()
          }
        }
      },
      immediate: true,
    },
      immediate: true
    }
  },
  created() {
  created () {
    this.mapPromise = AMapLoader.load({
      key: "232929db1cb31f26d42b3645a7456154",
      version: "2.0",
      zoom: 25,
    });
      key: '232929db1cb31f26d42b3645a7456154',
      version: '2.0',
      zoom: 25
    })
    if (this.$route.query.projectId) {
      this.projectId = this.$route.query.projectId || "";
      this.projectId = this.$route.query.projectId || ''
      // 添加错误处理,确保登录信息获取失败不影响其他模块
      getLoginInfo({ id: this.projectId, type: 2 })
        .then((res) => {
          this.projectName = res.projectName;
          if (sessionStorage.getItem("userInfo")) {
          this.projectName = res.projectName
          if (sessionStorage.getItem('userInfo')) {
            this.permissionProject =
              JSON.parse(sessionStorage.getItem("permissionProject")) || [];
              JSON.parse(sessionStorage.getItem('permissionProject')) || []
          } else {
            const permissions = res.permissionProject
              ? res.permissionProject.split(",")
              : [];
            this.permissionProject = permissions;
              ? res.permissionProject.split(',')
              : []
            this.permissionProject = permissions
            sessionStorage.setItem(
              "permissionProject",
              'permissionProject',
              JSON.stringify(permissions)
            );
            )
          }
          sessionStorage.setItem("deviceTemplateId", res.deviceTemplateId); // 设备模板id
          sessionStorage.setItem("deviceTemplateName", res.deviceTemplateName); // 设备模板id
          sessionStorage.setItem("safetyId", res.safetyId); // 安全报告模板id
          sessionStorage.setItem("safetyName", res.safetyName);
          sessionStorage.setItem("bigHiddenDanger", res.bigHiddenDanger); // 1开启 2关闭 重大隐患
          sessionStorage.setItem('deviceTemplateId', res.deviceTemplateId) // 设备模板id
          sessionStorage.setItem('deviceTemplateName', res.deviceTemplateName) // 设备模板id
          sessionStorage.setItem('safetyId', res.safetyId) // 安全报告模板id
          sessionStorage.setItem('safetyName', res.safetyName)
          sessionStorage.setItem('bigHiddenDanger', res.bigHiddenDanger) // 1开启 2关闭 重大隐患
          sessionStorage.setItem(
            "generalHiddenDanger",
            'generalHiddenDanger',
            res.generalHiddenDanger
          ); // 1开启 2关闭 一般隐患
          sessionStorage.setItem("commentType", res.commentType); // 1模糊评价 2打分评价
          sessionStorage.setItem("clientName", res.name);
          sessionStorage.setItem("clientLogo", res.logo);
          this.clientName = res.name;
          this.clientLogo = res.logo;
          ) // 1开启 2关闭 一般隐患
          sessionStorage.setItem('commentType', res.commentType) // 1模糊评价 2打分评价
          sessionStorage.setItem('clientName', res.name)
          sessionStorage.setItem('clientLogo', res.logo)
          this.clientName = res.name
          this.clientLogo = res.logo
        })
        .catch((err) => {
          console.error("获取登录信息失败:", err);
          console.error('获取登录信息失败:', err)
          // 设置默认值,确保页面可以继续渲染
          this.projectName = "项目详情";
          this.projectName = '项目详情'
          // this.permissionProject = []
        });
      this.getDetail();
      this.getDangerTrendList();
      this.getProjectRiskTrend();
        })
      this.getDetail()
      this.getDangerTrendList()
      this.getProjectRiskTrend()
    }
  },
  mounted() {
  mounted () {
    setTimeout(() => {
      this.initRiskChart();
    }, 1000);
      this.initRiskChart()
    }, 1000)
  },
  activated() {
  activated () {
    // 当从其他页面返回时,重新设置地图 marker
    if (this.mapView && this.projectInfo.type !== undefined) {
      this.updateMapMarker();
      this.updateMapMarker()
    }
  },
  beforeDestroy() {
  beforeDestroy () {
    if (this._mapResizeHandler) {
      window.removeEventListener("resize", this._mapResizeHandler);
      this._mapResizeHandler = null;
      window.removeEventListener('resize', this._mapResizeHandler)
      this._mapResizeHandler = null
    }
    if (this.mapView) {
      this.mapView.destroy();
      this.mapView = null;
      this.mapView.destroy()
      this.mapView = null
    }
    this.currentMarker = null;
    this._lastMarkerPosKey = "";
    this.currentMarker = null
    this._lastMarkerPosKey = ''
  },
  methods: {
    // 打开项目进度弹窗
    toProjectDetail() {
      this.progressVisible = true;
      this.getLatestProgress();
    toProjectDetail () {
      this.progressVisible = true
      this.getLatestProgress()
    },
    // 获取最新进度
    async getLatestProgress() {
      this.progressLoading = true;
    async getLatestProgress () {
      this.progressLoading = true
      try {
        const res = await getProjectLatestProgress(this.projectId);
        const res = await getProjectLatestProgress(this.projectId)
        if (res) {
          this.latestProgress = res;
          this.latestProgress = res
          // 解析并检测媒体文件类型
          await this.detectMediaTypes(res.url);
          await this.detectMediaTypes(res.url)
        } else {
          this.latestProgress = {};
          this.progressMediaList = [];
          this.latestProgress = {}
          this.progressMediaList = []
        }
      } catch (error) {
        console.error("获取最新进度失败:", error);
        this.$message.error("获取最新进度失败");
        this.latestProgress = {};
        this.progressMediaList = [];
        console.error('获取最新进度失败:', error)
        this.$message.error('获取最新进度失败')
        this.latestProgress = {}
        this.progressMediaList = []
      } finally {
        this.progressLoading = false;
        this.progressLoading = false
      }
    },
    // 关闭进度弹窗
    closeProgressDialog() {
      this.progressVisible = false;
      this.latestProgress = {};
    closeProgressDialog () {
      this.progressVisible = false
      this.latestProgress = {}
    },
    // 跳转到历史进度页面
    toProgressHistory() {
      this.progressVisible = false;
    toProgressHistory () {
      this.progressVisible = false
      this.$router.push({
        path: "/projectProgress",
        path: '/projectProgress',
        query: {
          projectId: this.projectId,
          projectName: this.projectName,
        },
      });
          projectName: this.projectName
        }
      })
    },
    // 格式化进度时间
    formatProgressTime(time) {
      return time ? moment(time).format("YYYY-MM-DD HH:mm:ss") : "-";
    formatProgressTime (time) {
      return time ? moment(time).format('YYYY-MM-DD HH:mm:ss') : '-'
    },
    // 通过 Content-Type 检测媒体文件类型(加载时检测所有文件)
    async detectMediaTypes(urlStr) {
    async detectMediaTypes (urlStr) {
      if (!urlStr) {
        this.progressMediaList = [];
        return;
        this.progressMediaList = []
        return
      }
      try {
        let fileIds = [];
        let fileIds = []
        // 如果是数组,直接使用
        if (Array.isArray(urlStr)) {
          fileIds = urlStr;
          fileIds = urlStr
        }
        // 如果是逗号分隔的字符串
        else if (typeof urlStr === "string" && urlStr.includes(",")) {
          fileIds = urlStr.split(",").filter((item) => item.trim());
        else if (typeof urlStr === 'string' && urlStr.includes(',')) {
          fileIds = urlStr.split(',').filter((item) => item.trim())
        }
        // 如果是单个字符串
        else if (typeof urlStr === "string" && urlStr.trim()) {
          fileIds = [urlStr.trim()];
        else if (typeof urlStr === 'string' && urlStr.trim()) {
          fileIds = [urlStr.trim()]
        }
        // 并发检测所有文件的类型
        const mediaPromises = fileIds.map(async (id) => {
          let url = "";
          let url = ''
          // 如果已经是完整的URL(http或https开头),直接使用
          if (id.startsWith("http://") || id.startsWith("https://")) {
            url = id;
          if (id.startsWith('http://') || id.startsWith('https://')) {
            url = id
          } else {
            // 否则拼接为下载URL
            url = `${this.VUE_APP_BASE_URL}/project/download/${id}`;
            url = `${this.VUE_APP_BASE_URL}/project/download/${id}`
          }
          // 先尝试从扩展名判断
          let type = "image";
          let type = 'image'
          if (url.match(/\.(mp4|webm|ogg|avi|mov)$/i)) {
            type = "video";
            type = 'video'
          } else {
            // 如果没有扩展名,通过 Content-Type 检测
            type = await this.getFileTypeByContentType(url);
            type = await this.getFileTypeByContentType(url)
          }
          return {
            url: url,
            type: type, // 'image' 或 'video'
            id: id,
          };
        });
            id: id
          }
        })
        this.progressMediaList = await Promise.all(mediaPromises);
        this.progressMediaList = await Promise.all(mediaPromises)
      } catch (error) {
        console.error("检测媒体文件类型失败:", error);
        this.progressMediaList = [];
        console.error('检测媒体文件类型失败:', error)
        this.progressMediaList = []
      }
    },
    // 通过 Content-Type 判断文件类型(使用 GET 请求,和后台管理保持一致)
    async getFileTypeByContentType(url) {
    async getFileTypeByContentType (url) {
      try {
        // 使用 GET 请求获取响应头(和 React 后台管理一致)
        const response = await fetch(url, {
          method: "GET",
          mode: "cors",
        });
          method: 'GET',
          mode: 'cors'
        })
        const contentType = response.headers.get("Content-Type") || "";
        const contentType = response.headers.get('Content-Type') || ''
        // 判断是否为视频
        if (contentType.startsWith("video/")) {
          return "video";
        if (contentType.startsWith('video/')) {
          return 'video'
        }
        // 判断是否为图片
        else if (contentType.startsWith("image/")) {
          return "image";
        else if (contentType.startsWith('image/')) {
          return 'image'
        }
        // 默认当作图片处理
        else {
          return "image";
          return 'image'
        }
      } catch (error) {
        console.error("获取文件 Content-Type 失败:", url, error);
        console.error('获取文件 Content-Type 失败:', url, error)
        // 如果请求失败,默认当作图片处理
        return "image";
        return 'image'
      }
    },
    // 预览进度媒体(图片或视频)- 弹窗预览
    previewProgressMedia(item) {
    previewProgressMedia (item) {
      if (item && item.url) {
        this.previewUrl = item.url;
        this.previewType = item.type || "image";
        this.previewVisible = true;
        this.previewUrl = item.url
        this.previewType = item.type || 'image'
        this.previewVisible = true
      }
    },
    // 关闭预览弹窗
    closePreview() {
      this.previewVisible = false;
      this.previewUrl = "";
      this.previewType = "image";
    closePreview () {
      this.previewVisible = false
      this.previewUrl = ''
      this.previewType = 'image'
    },
    // 图片加载失败处理
    handleImageError(e) {
      e.target.src = require("../../assets/img/logo.png");
    handleImageError (e) {
      e.target.src = require('../../assets/img/logo.png')
    },
    // 视频加载失败处理
    handleVideoError(e) {
      console.error("视频加载失败:", e);
    handleVideoError (e) {
      console.error('视频加载失败:', e)
      // 可以在这里添加视频加载失败的处理逻辑
    },
    // 获取项目详情
    getDetail() {
    getDetail () {
      // 设备类型 - 添加错误处理
      deviceTypes()
        .then((res) => {
          this.deviceTypeList = res;
          this.deviceTypeList = res
        })
        .catch((err) => {
          console.error("获取设备类型失败:", err);
          this.deviceTypeList = [];
        });
          console.error('获取设备类型失败:', err)
          this.deviceTypeList = []
        })
      // 获取项目详情 - 添加错误处理
      getProjectDetail(this.projectId)
        .then((res) => {
          const addressInfo = res.addressInfo
            ? JSON.parse(res.addressInfo)
            : {};
            : {}
          const constructionCompanyInfo = res.constructionCompanyInfo
            ? JSON.parse(res.constructionCompanyInfo)
            : {};
            : {}
          const developmentCompanyInfo = res.developmentCompanyInfo
            ? JSON.parse(res.developmentCompanyInfo)
            : {};
            : {}
          const supervisorCompanyInfo = res.supervisorCompanyInfo
            ? JSON.parse(res.supervisorCompanyInfo)
            : {};
            : {}
          this.projectInfo = {
            ...res,
            addressInfo: addressInfo,
            address:
              (addressInfo.province || "") +
              (addressInfo.city || "") +
              (addressInfo.district || "") +
              (addressInfo.detail || ""),
              (addressInfo.province || '') +
              (addressInfo.city || '') +
              (addressInfo.district || '') +
              (addressInfo.detail || ''),
            constructionCompanyInfo: constructionCompanyInfo,
            developmentCompanyInfo: developmentCompanyInfo,
            supervisorCompanyInfo: supervisorCompanyInfo,
          };
            supervisorCompanyInfo: supervisorCompanyInfo
          }
          this.$nextTick(() => {
            this.setMapload().then(() => {
              this.updateMapMarker();
            });
          });
              this.updateMapMarker()
            })
          })
        })
        .catch((err) => {
          console.error("获取项目详情失败:", err);
          console.error('获取项目详情失败:', err)
          // 设置默认值
          this.projectInfo = {
            addressInfo: {},
            address: "",
            address: '',
            constructionCompanyInfo: {},
            developmentCompanyInfo: {},
            supervisorCompanyInfo: {},
            properties: [],
          };
        });
            properties: []
          }
        })
      // 设备数量 - 添加错误处理
      getProjectDeviceCount(this.projectId)
        .then((res) => {
          this.equipment = 0;
          this.equipment = 0
          this.legendData = res.map((item) => {
            this.equipment += item.count;
            const deviceType = this.deviceTypeList[item.type];
            this.equipment += item.count
            const deviceType = this.deviceTypeList[item.type]
            return {
              ...item,
              value: item.count || 0,
              name:
                deviceType && deviceType.name ? deviceType.name : "未知设备",
              itemStyle: { color: this.generateRandomColor() },
            };
          });
          this.initPie3D();
                deviceType && deviceType.name ? deviceType.name : '未知设备',
              itemStyle: { color: this.generateRandomColor() }
            }
          })
          this.initPie3D()
        })
        .catch((err) => {
          console.error("获取设备数量失败:", err);
          this.equipment = 0;
          this.legendData = [];
        });
          console.error('获取设备数量失败:', err)
          this.equipment = 0
          this.legendData = []
        })
      // 隐患数量 - 添加错误处理
      getDangerCount(this.projectId)
        .then((res) => {
          this.dangerCount = res;
          this.dangerCount = res
        })
        .catch((err) => {
          console.error("获取隐患数量失败:", err);
          console.error('获取隐患数量失败:', err)
          this.dangerCount = {
            serious: 0,
            rectified: 0,
            unRectified: 0,
          };
        });
            unRectified: 0
          }
        })
      // 项目人员信息 - 添加错误处理
      getProjectPersonInfo({ projectId: this.projectId })
        .then((res) => {
          this.peopleList = res || [];
          this.peopleList = res || []
        })
        .catch((err) => {
          console.error("获取人员信息失败:", err);
          this.peopleList = [];
        });
          console.error('获取人员信息失败:', err)
          this.peopleList = []
        })
      // 获取项目风险源 - 添加错误处理
      getProjectSource({ projectId: this.projectId })
        .then((res) => {
          const riskSourceList = [];
          const riskSourceList = []
          // 处理超危大工程
          if (res.superDangerousOptions) {
            try {
              const superDangerousArray = JSON.parse(res.superDangerousOptions);
              const superDangerousArray = JSON.parse(res.superDangerousOptions)
              const transformedData = transformRiskSourceData(
                superDangerousArray,
                "superDangerous",
                "超危大工程"
              );
              riskSourceList.push(...transformedData);
                'superDangerous',
                '超危大工程'
              )
              riskSourceList.push(...transformedData)
            } catch (e) {
              console.error("解析超危大工程数据失败:", e);
              console.error('解析超危大工程数据失败:', e)
            }
          }
          // 处理危大工程
          if (res.dangerousOptions) {
            try {
              const dangerousArray = JSON.parse(res.dangerousOptions);
              const dangerousArray = JSON.parse(res.dangerousOptions)
              const transformedData = transformRiskSourceData(
                dangerousArray,
                "dangerous",
                "危大工程"
              );
              riskSourceList.push(...transformedData);
                'dangerous',
                '危大工程'
              )
              riskSourceList.push(...transformedData)
            } catch (e) {
              console.error("解析危大工程数据失败:", e);
              console.error('解析危大工程数据失败:', e)
            }
          }
          // 处理风险源
          if (res.hazardSource) {
            try {
              const hazardSourceArray = JSON.parse(res.hazardSource);
              const hazardSourceArray = JSON.parse(res.hazardSource)
              const transformedData = transformRiskSourceData(
                hazardSourceArray,
                "hazardSource",
                "风险源"
              );
              riskSourceList.push(...transformedData);
                'hazardSource',
                '风险源'
              )
              riskSourceList.push(...transformedData)
            } catch (e) {
              console.error("解析风险源数据失败:", e);
              console.error('解析风险源数据失败:', e)
            }
          }
          this.riskSourceData = riskSourceList;
          this.riskSourceData = riskSourceList
        })
        .catch((err) => {
          console.error("获取项目风险源失败:", err);
          this.riskSourceData = [];
        });
          console.error('获取项目风险源失败:', err)
          this.riskSourceData = []
        })
    },
    // 获取隐患趋势
    getDangerTrendList() {
    getDangerTrendList () {
      let params = {
        projectId: this.projectId,
        type: this.dangerSortType, // 1数量 2 趋势
        desc: this.dangerSortDesc, // 是否倒序
      };
        desc: this.dangerSortDesc // 是否倒序
      }
      getDangerTrend(params)
        .then((res) => {
          this.dangerTrendList = res || [];
          this.dangerTrendList = res || []
        })
        .catch((err) => {
          console.error("获取隐患趋势失败:", err);
          this.dangerTrendList = [];
        });
          console.error('获取隐患趋势失败:', err)
          this.dangerTrendList = []
        })
    },
    // 切换隐患排序方式
    changeDangerSort(type) {
    changeDangerSort (type) {
      if (this.dangerSortType === type) {
        // 如果点击的是当前排序类型,则切换升序/降序
        this.dangerSortDesc = !this.dangerSortDesc;
        this.dangerSortDesc = !this.dangerSortDesc
      } else {
        // 如果点击的是不同的排序类型,则切换类型并默认倒序
        this.dangerSortType = type;
        this.dangerSortDesc = true;
        this.dangerSortType = type
        this.dangerSortDesc = true
      }
      // 重新请求数据
      this.getDangerTrendList();
      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`;
    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() {
    toPersonnelOnDuty () {
      this.$router.push({
        path: "/personnelOnDuty",
        path: '/personnelOnDuty',
        query: {
          projectId: this.projectId,
          projectName: this.projectName,
        },
      });
          projectName: this.projectName
        }
      })
    },
    // 跳转隐患列表
    toHiddenDanger(type) {
    toHiddenDanger (type) {
      this.$router.push({
        path: "/hiddenDanger",
        path: '/hiddenDanger',
        query: {
          projectId: this.projectId,
          projectName: this.projectName,
          activeTab: type,
          type: 2,
        },
      });
          type: 2
        }
      })
    },
    // 跳转设备列表
    toEquipmentInfo() {
    toEquipmentInfo () {
      this.$router.push({
        path: "/equipmentList",
        path: '/equipmentList',
        query: {
          projectId: this.projectId,
          projectName: this.projectName,
        },
      });
          projectName: this.projectName
        }
      })
    },
    // 跳转重大危险源
    toMajorRiskList() {
    toMajorRiskList () {
      this.$router.push({
        path: "/majorRisk",
        path: '/majorRisk',
        query: {
          projectId: this.projectId,
          projectName: this.projectName
        }
      })
    },
    toPlanOfAudit () {
      this.$router.push({
        path: '/planOfAudit',
        query: {
          projectId: this.projectId,
          projectName: this.projectName,
        },
      });
          type: 2
        }
      })
    },
    toPlanOfAudit() {
      this.$router.push({
        path: "/planOfAudit",
        query: {
          projectId: this.projectId,
          projectName: this.projectName,
          type: 2,
        },
      });
    },
    setMapload() {
    setMapload () {
      if (!this.mapPromise) {
        this.mapPromise = AMapLoader.load({
          key: "232929db1cb31f26d42b3645a7456154",
          version: "2.0",
        });
          key: '232929db1cb31f26d42b3645a7456154',
          version: '2.0'
        })
      }
      if (this._mapInitPromise) {
        return this._mapInitPromise;
        return this._mapInitPromise
      }
      this._mapInitPromise = this.mapPromise
        .then((AMap) => {
          this._AMap = AMap;
          this._AMap = AMap
          if (this.mapView) {
            this.mapView.resize();
            this.mapView.resize()
            if (!this._mapResizeHandler) {
              this._mapResizeHandler = () => {
                if (this.mapView) this.mapView.resize();
              };
              window.addEventListener("resize", this._mapResizeHandler);
                if (this.mapView) this.mapView.resize()
              }
              window.addEventListener('resize', this._mapResizeHandler)
            }
            return AMap;
            return AMap
          }
          const satellite = new AMap.TileLayer.Satellite();
          const satellite = new AMap.TileLayer.Satellite()
          // 获取经纬度
          let center = [104.065617, 30.65695]; // 默认中心点
          let longitude = null;
          let latitude = null;
          let center = [104.065617, 30.65695] // 默认中心点
          let longitude = null
          let latitude = null
          if (this.projectInfo && this.projectInfo.addressInfo) {
            longitude = Number(this.projectInfo.addressInfo.longitude);
            latitude = Number(this.projectInfo.addressInfo.latitude);
            longitude = Number(this.projectInfo.addressInfo.longitude)
            latitude = Number(this.projectInfo.addressInfo.latitude)
            if (!isNaN(longitude) && !isNaN(latitude) && longitude !== 0 && latitude !== 0) {
              center = [longitude, latitude];
              center = [longitude, latitude]
            }
          }
          this.mapView = new AMap.Map("projectContainer", {
          this.mapView = new AMap.Map('projectContainer', {
            zoom: 17,
            center: center,
            layers: [satellite],
          });
            layers: [satellite]
          })
          this.mapView.resize();
          this.mapView.resize()
          if (!this._mapResizeHandler) {
            this._mapResizeHandler = () => {
              if (this.mapView) this.mapView.resize();
            };
            window.addEventListener("resize", this._mapResizeHandler);
              if (this.mapView) this.mapView.resize()
            }
            window.addEventListener('resize', this._mapResizeHandler)
          }
          return AMap;
          return AMap
        })
        .catch((e) => {
          console.error(e);
          throw e;
          console.error(e)
          throw e
        })
        .finally(() => {
          this._mapInitPromise = null;
        });
          this._mapInitPromise = null
        })
      return this._mapInitPromise;
      return this._mapInitPromise
    },
    // 更新地图 marker
    updateMapMarker() {
      if (!this.mapView) return;
    updateMapMarker () {
      if (!this.mapView) return
      const AMap = this._AMap || window.AMap;
      if (!AMap) return;
      const AMap = this._AMap || window.AMap
      if (!AMap) return
      // 根据项目类型选择图标
      const iconImages = [
        require("../../assets/img/home/f4.png"),
        require("../../assets/img/home/s4.png"),
        require("../../assets/img/home/z4.png"),
      ];
        require('../../assets/img/home/f4.png'),
        require('../../assets/img/home/s4.png'),
        require('../../assets/img/home/z4.png')
      ]
      const iconImage = iconImages[this.projectInfo.type] || iconImages[0];
      const iconImage = iconImages[this.projectInfo.type] || iconImages[0]
      const tryRenderMarker = (lng, lat) => {
        if (
          typeof lng !== "number" ||
          typeof lat !== "number" ||
          typeof lng !== 'number' ||
          typeof lat !== 'number' ||
          isNaN(lng) ||
          isNaN(lat)
        ) {
          return false;
          return false
        }
        const nextKey = `${lng},${lat}`;
        const nextKey = `${lng},${lat}`
        if (this.currentMarker && this._lastMarkerPosKey === nextKey) {
          return true;
          return true
        }
        if (!this.currentMarker) {
@@ -1420,59 +1420,59 @@
            icon: new AMap.Icon({
              image: iconImage,
              size: new AMap.Size(30, 30),
              imageSize: new AMap.Size(30, 30),
              imageSize: new AMap.Size(30, 30)
            }),
            offset: new AMap.Pixel(-15, -15),
            zoom: 17,
          });
          this.mapView.add(this.currentMarker);
        } else if (typeof this.currentMarker.setPosition === "function") {
          this.currentMarker.setPosition([lng, lat]);
            zoom: 17
          })
          this.mapView.add(this.currentMarker)
        } else if (typeof this.currentMarker.setPosition === 'function') {
          this.currentMarker.setPosition([lng, lat])
        } else {
          this.mapView.remove(this.currentMarker);
          this.mapView.remove(this.currentMarker)
          this.currentMarker = new AMap.Marker({
            position: [lng, lat],
            icon: new AMap.Icon({
              image: iconImage,
              size: new AMap.Size(30, 30),
              imageSize: new AMap.Size(30, 30),
              imageSize: new AMap.Size(30, 30)
            }),
            offset: new AMap.Pixel(-15, -15),
            zoom: 17,
          });
          this.mapView.add(this.currentMarker);
            zoom: 17
          })
          this.mapView.add(this.currentMarker)
        }
        if (typeof this.mapView.setZoomAndCenter === "function") {
        if (typeof this.mapView.setZoomAndCenter === 'function') {
          if (!this._lastMarkerPosKey) {
            this.mapView.setZoomAndCenter(17, [lng, lat]);
          } else if (typeof this.mapView.setCenter === "function") {
            this.mapView.setCenter([lng, lat]);
            this.mapView.setZoomAndCenter(17, [lng, lat])
          } else if (typeof this.mapView.setCenter === 'function') {
            this.mapView.setCenter([lng, lat])
          } else {
            this.mapView.setZoomAndCenter(17, [lng, lat]);
            this.mapView.setZoomAndCenter(17, [lng, lat])
          }
        }
        this._lastMarkerPosKey = nextKey;
        return true;
      };
        this._lastMarkerPosKey = nextKey
        return true
      }
      // 1) 优先使用后端返回的经纬度(如果有)
      const info = this.projectInfo.addressInfo || {};
      let lng = null;
      let lat = null;
      const info = this.projectInfo.addressInfo || {}
      let lng = null
      let lat = null
      if (info.longitude && info.latitude) {
        lng = Number(info.longitude);
        lat = Number(info.latitude);
        lng = Number(info.longitude)
        lat = Number(info.latitude)
      } else {
        const lonLatStr = typeof info.lonLat === "string" ? info.lonLat : "";
        const lonLatParts = lonLatStr.split(",");
        lng = lonLatParts[0] ? Number(lonLatParts[0]) : null;
        lat = lonLatParts[1] ? Number(lonLatParts[1]) : null;
        const lonLatStr = typeof info.lonLat === 'string' ? info.lonLat : ''
        const lonLatParts = lonLatStr.split(',')
        lng = lonLatParts[0] ? Number(lonLatParts[0]) : null
        lat = lonLatParts[1] ? Number(lonLatParts[1]) : null
      }
      if (tryRenderMarker(lng, lat)) {
        return;
        return
      }
      // 2) 无经纬度时,使用地址进行地理编码
@@ -1482,149 +1482,149 @@
          info && info.province,
          info && info.city,
          info && info.district,
          info && info.detail,
          info && info.detail
        ]
          .filter(Boolean)
          .join("");
          .join('')
      if (address && this.mapPromise) {
        this.mapPromise
          .then(() => {
            const Geocoder =
              (window.AMap && window.AMap.Geocoder) ||
              (typeof AMap !== "undefined" && AMap.Geocoder);
            if (!Geocoder) return;
              (typeof AMap !== 'undefined' && AMap.Geocoder)
            if (!Geocoder) return
            const geocoder = new Geocoder({
              city: (info && (info.city || info.province)) || "",
            });
              city: (info && (info.city || info.province)) || ''
            })
            geocoder.getLocation(address, (status, result) => {
              if (
                status === "complete" &&
                status === 'complete' &&
                result &&
                result.geocodes &&
                result.geocodes.length > 0
              ) {
                const loc = result.geocodes[0].location;
                const lnglat = [loc.lng, loc.lat];
                tryRenderMarker(lnglat[0], lnglat[1]);
                const loc = result.geocodes[0].location
                const lnglat = [loc.lng, loc.lat]
                tryRenderMarker(lnglat[0], lnglat[1])
              } else {
                // 回退到默认中心点
                tryRenderMarker(104.065617, 30.65695);
                tryRenderMarker(104.065617, 30.65695)
              }
            });
            })
          })
          .catch(() => {
            // 加载失败时回退到默认中心点
            tryRenderMarker(104.065617, 30.65695);
          });
            tryRenderMarker(104.065617, 30.65695)
          })
      } else {
        // 3) 地址不可用时,回退到默认中心点
        tryRenderMarker(104.065617, 30.65695);
        tryRenderMarker(104.065617, 30.65695)
      }
    },
    // 初始化 3D 环饼图(echarts-gl)
    initPie3D() {
      const el = this.$refs.pieChart;
      if (!el) return;
      this._pie = echarts.init(el);
    initPie3D () {
      const el = this.$refs.pieChart
      if (!el) return
      this._pie = echarts.init(el)
      // 使用动态数据或默认数据
      const data = this.legendData.length > 0 ? this.legendData : [];
      const data = this.legendData.length > 0 ? this.legendData : []
      // 生成 3D 饼图 option(internalDiameterRatio 控制环形内径比)
      this.legendData = data; // 用于右侧自定义 legend
      const series = this.getPie3D(data, 0.7);
      this.legendData = data // 用于右侧自定义 legend
      const series = this.getPie3D(data, 0.7)
      const option = {
        legend: {
          tooltip: {
            show: true,
            show: true
          },
          data: data.map((item) => item.name),
          right: "2%",
          top: "center",
          orient: "vertical",
          right: '2%',
          top: 'center',
          orient: 'vertical',
          itemGap: 20,
          textStyle: {
            color: "#fff",
            fontSize: 12,
            color: '#fff',
            fontSize: 12
          },
          show: false,
          show: false
        },
        graphic: {
          elements: [
            {
              type: "circle",
              type: 'circle',
              shape: {
                cx: "50%",
                cy: "50%",
                r: "35%",
                cx: '50%',
                cy: '50%',
                r: '35%'
              },
              style: {
                fill: "transparent",
                fill: 'transparent',
                shadowBlur: 20,
                shadowColor: "rgba(0, 191, 255, 0.4)",
                opacity: 0.6,
                shadowColor: 'rgba(0, 191, 255, 0.4)',
                opacity: 0.6
              },
              z: -1,
            },
          ],
              z: -1
            }
          ]
        },
        animation: true,
        tooltip: {
          formatter: (params) => {
            if (
              params.seriesName !== "mouseoutSeries" &&
              params.seriesName !== "pie2d"
              params.seriesName !== 'mouseoutSeries' &&
              params.seriesName !== 'pie2d'
            ) {
              const series = option.series[params.seriesIndex];
              const series = option.series[params.seriesIndex]
              if (series && series.pieData) {
                return `${params.seriesName}<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>${series.pieData.value}`;
                return `${params.seriesName}<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>${series.pieData.value}`
              }
            }
          },
          textStyle: {
            fontSize: 12,
          },
            fontSize: 12
          }
        },
        title: {
          x: "center",
          top: "20",
          x: 'center',
          top: '20',
          textStyle: {
            color: "#fff",
            fontSize: 12,
          },
            color: '#fff',
            fontSize: 12
          }
        },
        backgroundColor: "transparent",
        backgroundColor: 'transparent',
        labelLine: {
          show: false,
          lineStyle: {
            color: "transparent",
          },
            color: 'transparent'
          }
        },
        label: {
          show: false,
          color: "transparent",
          position: "outside",
          formatter: "{b} \n{c} {d}%",
          color: 'transparent',
          position: 'outside',
          formatter: '{b} \n{c} {d}%'
        },
        xAxis3D: {
          min: -1,
          max: 1,
          max: 1
        },
        yAxis3D: {
          min: -1,
          max: 1,
          max: 1
        },
        zAxis3D: {
          min: -1,
          max: 1,
          max: 1
        },
        grid3D: {
          show: false,
          boxHeight: 2,
          bottom: "-10%",
          left: "-15%",
          bottom: '-10%',
          left: '-15%',
          viewControl: {
            alpha: 18,
            beta: 50,
@@ -1632,32 +1632,32 @@
            autoRotate: true,
            rotateSensitivity: 0,
            zoomSensitivity: 0,
            panSensitivity: 0,
          },
            panSensitivity: 0
          }
        },
        series: series,
      };
        series: series
      }
      this._pie.clear();
      this._pie.setOption(option);
      this._pie.clear()
      this._pie.setOption(option)
      // 鼠标事件交互
      let selectedIndex = "";
      let hoveredIndex = "";
      let selectedIndex = ''
      let hoveredIndex = ''
      this._pie.on("click", (params) => {
        if (params.seriesName === "mouseoutSeries") return;
        const s = option.series[params.seriesIndex];
        if (!s || !s.pieData || !s.pieStatus) return;
      this._pie.on('click', (params) => {
        if (params.seriesName === 'mouseoutSeries') return
        const s = option.series[params.seriesIndex]
        if (!s || !s.pieData || !s.pieStatus) return
        const isSelected = !s.pieStatus.selected;
        const isHovered = s.pieStatus.hovered;
        const k = s.pieStatus.k;
        const startRatio = s.pieData.startRatio;
        const endRatio = s.pieData.endRatio;
        const isSelected = !s.pieStatus.selected
        const isHovered = s.pieStatus.hovered
        const k = s.pieStatus.k
        const startRatio = s.pieData.startRatio
        const endRatio = s.pieData.endRatio
        if (selectedIndex !== "" && selectedIndex !== params.seriesIndex) {
          const prev = option.series[selectedIndex];
        if (selectedIndex !== '' && selectedIndex !== params.seriesIndex) {
          const prev = option.series[selectedIndex]
          if (prev && prev.pieData && prev.pieStatus) {
            prev.parametricEquation = this.getParametricEquation(
              prev.pieData.startRatio,
@@ -1666,8 +1666,8 @@
              false,
              k,
              prev.pieData.value
            );
            prev.pieStatus.selected = false;
            )
            prev.pieStatus.selected = false
          }
        }
@@ -1678,21 +1678,21 @@
          isHovered,
          k,
          s.pieData.value
        );
        s.pieStatus.selected = isSelected;
        if (isSelected) selectedIndex = params.seriesIndex;
        )
        s.pieStatus.selected = isSelected
        if (isSelected) selectedIndex = params.seriesIndex
        this._pie.setOption(option);
      });
        this._pie.setOption(option)
      })
      this._pie.on("mouseover", (params) => {
        if (hoveredIndex === params.seriesIndex) return;
      this._pie.on('mouseover', (params) => {
        if (hoveredIndex === params.seriesIndex) return
        // 取消之前的高亮
        if (hoveredIndex !== "") {
          let prev = option.series[hoveredIndex];
        if (hoveredIndex !== '') {
          let prev = option.series[hoveredIndex]
          if (prev && prev.pieData && prev.pieStatus) {
            const k = prev.pieStatus.k;
            const k = prev.pieStatus.k
            prev.parametricEquation = this.getParametricEquation(
              prev.pieData.startRatio,
              prev.pieData.endRatio,
@@ -1700,17 +1700,17 @@
              false,
              k,
              prev.pieData.value
            );
            prev.pieStatus.hovered = false;
            hoveredIndex = "";
            )
            prev.pieStatus.hovered = false
            hoveredIndex = ''
          }
        }
        // 当前高亮(排除透明圆环)
        if (params.seriesName !== "mouseoutSeries") {
          let cur = option.series[params.seriesIndex];
        if (params.seriesName !== 'mouseoutSeries') {
          let cur = option.series[params.seriesIndex]
          if (cur && cur.pieData && cur.pieStatus) {
            const k = cur.pieStatus.k;
            const k = cur.pieStatus.k
            cur.parametricEquation = this.getParametricEquation(
              cur.pieData.startRatio,
              cur.pieData.endRatio,
@@ -1718,20 +1718,20 @@
              true,
              k,
              cur.pieData.value
            );
            cur.pieStatus.hovered = true;
            hoveredIndex = params.seriesIndex;
            )
            cur.pieStatus.hovered = true
            hoveredIndex = params.seriesIndex
          }
        }
        this._pie.setOption(option);
      });
        this._pie.setOption(option)
      })
      this._pie.on("globalout", () => {
        if (hoveredIndex !== "") {
          let prev = option.series[hoveredIndex];
      this._pie.on('globalout', () => {
        if (hoveredIndex !== '') {
          let prev = option.series[hoveredIndex]
          if (prev && prev.pieData && prev.pieStatus) {
            const k = prev.pieStatus.k;
            const k = prev.pieStatus.k
            prev.parametricEquation = this.getParametricEquation(
              prev.pieData.startRatio,
              prev.pieData.endRatio,
@@ -1739,23 +1739,23 @@
              false,
              k,
              prev.pieData.value
            );
            prev.pieStatus.hovered = false;
            hoveredIndex = "";
            this._pie.setOption(option);
            )
            prev.pieStatus.hovered = false
            hoveredIndex = ''
            this._pie.setOption(option)
          }
        }
      });
      })
    },
    // 生成随机颜色
    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}%)`;
    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}%)`
    },
    // 生成扇形的曲面参数方程
    getParametricEquation(
    getParametricEquation (
      startRatio,
      endRatio,
      isSelected,
@@ -1764,135 +1764,135 @@
      height
    ) {
      // 计算
      let midRatio = (startRatio + endRatio) / 2;
      let midRatio = (startRatio + endRatio) / 2
      let startRadian = startRatio * Math.PI * 2;
      let endRadian = endRatio * Math.PI * 2;
      let midRadian = midRatio * Math.PI * 2;
      let startRadian = startRatio * Math.PI * 2
      let endRadian = endRatio * Math.PI * 2
      let midRadian = midRatio * Math.PI * 2
      // 如果只有一个扇形,则不实现选中效果。
      if (startRatio === 0 && endRatio === 1) {
        isSelected = false;
        isSelected = false
      }
      // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
      k = typeof k !== "undefined" ? k : 1 / 3;
      k = typeof k !== 'undefined' ? k : 1 / 3
      // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)
      let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
      let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
      let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0
      let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0
      // 计算高亮效果的放大比例(未高亮,则比例为 1)
      let hoverRate = isHovered ? 1.05 : 1;
      let hoverRate = isHovered ? 1.05 : 1
      // 返回曲面参数方程
      return {
        u: {
          min: -Math.PI,
          max: Math.PI * 3,
          step: Math.PI / 32,
          step: Math.PI / 32
        },
        v: {
          min: 0,
          max: Math.PI * 2,
          step: Math.PI / 20,
          step: Math.PI / 20
        },
        x: function (u, v) {
          if (u < startRadian) {
            return (
              offsetX +
              Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
            );
            )
          }
          if (u > endRadian) {
            return (
              offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
            );
            )
          }
          return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
          return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate
        },
        y: function (u, v) {
          if (u < startRadian) {
            return (
              offsetY +
              Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
            );
            )
          }
          if (u > endRadian) {
            return (
              offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
            );
            )
          }
          return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
          return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate
        },
        z: function (u, v) {
          if (u < -Math.PI * 0.5) {
            return Math.sin(u);
            return Math.sin(u)
          }
          if (u > Math.PI * 2.5) {
            return Math.sin(u);
            return Math.sin(u)
          }
          return Math.sin(v) > 0 ? 0.6 * height : -0.6;
        },
      };
          return Math.sin(v) > 0 ? 0.6 * height : -0.6
        }
      }
    },
    // 生成模拟 3D 饼图的配置项
    getPie3D(pieData, internalDiameterRatio) {
      let series = [];
      let sumValue = 0;
      let startValue = 0;
      let endValue = 0;
      let legendData = [];
    getPie3D (pieData, internalDiameterRatio) {
      let series = []
      let sumValue = 0
      let startValue = 0
      let endValue = 0
      let legendData = []
      let k =
        typeof internalDiameterRatio !== "undefined"
        typeof internalDiameterRatio !== 'undefined'
          ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio)
          : 1 / 3;
          : 1 / 3
      // 为每一个饼图数据,生成一个 series-surface 配置
      for (let i = 0; i < pieData.length; i++) {
        sumValue += pieData[i].value;
        sumValue += pieData[i].value
        let seriesItem = {
          name:
            typeof pieData[i].name === "undefined"
            typeof pieData[i].name === 'undefined'
              ? `series${i}`
              : pieData[i].name,
          type: "surface",
          type: 'surface',
          parametric: true,
          wireframe: {
            show: false,
            show: false
          },
          pieData: pieData[i],
          pieStatus: {
            selected: false,
            hovered: false,
            k: k,
          },
        };
        if (typeof pieData[i].itemStyle !== "undefined") {
          let itemStyle = {};
          typeof pieData[i].itemStyle.color !== "undefined"
            ? (itemStyle.color = pieData[i].itemStyle.color)
            : null;
          typeof pieData[i].itemStyle.opacity !== "undefined"
            ? (itemStyle.opacity = pieData[i].itemStyle.opacity)
            : null;
          seriesItem.itemStyle = itemStyle;
            k: k
          }
        }
        series.push(seriesItem);
        if (typeof pieData[i].itemStyle !== 'undefined') {
          let itemStyle = {}
          typeof pieData[i].itemStyle.color !== 'undefined'
            ? (itemStyle.color = pieData[i].itemStyle.color)
            : null
          typeof pieData[i].itemStyle.opacity !== 'undefined'
            ? (itemStyle.opacity = pieData[i].itemStyle.opacity)
            : null
          seriesItem.itemStyle = itemStyle
        }
        series.push(seriesItem)
      }
      // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
      // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
      for (let i = 0; i < series.length; i++) {
        endValue = startValue + series[i].pieData.value;
        endValue = startValue + series[i].pieData.value
        series[i].pieData.startRatio = startValue / sumValue;
        series[i].pieData.endRatio = endValue / sumValue;
        series[i].pieData.startRatio = startValue / sumValue
        series[i].pieData.endRatio = endValue / sumValue
        series[i].parametricEquation = this.getParametricEquation(
          series[i].pieData.startRatio,
          series[i].pieData.endRatio,
@@ -1900,11 +1900,11 @@
          false,
          k,
          series[i].pieData.value
        );
        )
        startValue = endValue;
        startValue = endValue
        legendData.push(series[i].name);
        legendData.push(series[i].name)
      }
      // series.push({
      //   name: 'mouseoutSeries',
@@ -1939,328 +1939,328 @@
      //     },
      //   },
      // });
      return series;
      return series
    },
    // 风险等级转换为数值 (A->3, B->2, C->1)
    levelToValue(level) {
      if (!level) return null;
      const levelStr = String(level).toUpperCase().trim();
    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;
      if (levelStr === 'A' || levelStr === 'A类') return 3
      if (levelStr === 'B' || levelStr === 'B类') return 2
      if (levelStr === 'C' || levelStr === 'C类') return 1
      return null;
      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 "";
    valueToLevel (value) {
      if (value === 3) return 'A类'
      if (value === 2) return 'B类'
      if (value === 1) return 'C类'
      return ''
    },
    // 获取项目风险趋势数据
    async getProjectRiskTrend() {
    async getProjectRiskTrend () {
      if (!this.dateRange || this.dateRange.length !== 2) {
        return;
        return
      }
      const params = {
        projectId: this.projectId,
        startTime: moment(this.dateRange[0]).format("YYYY-MM"),
        endTime: moment(this.dateRange[1]).format("YYYY-MM"),
      };
        startTime: moment(this.dateRange[0]).format('YYYY-MM'),
        endTime: moment(this.dateRange[1]).format('YYYY-MM')
      }
      try {
        const res = await getProjectDangerousTrend(params);
        const res = await getProjectDangerousTrend(params)
        if (res && Array.isArray(res)) {
          // 处理接口返回的数据
          this.riskTrendData.xAxisData = res.map((item) => item.time || "");
          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);
            const value = this.levelToValue(item.level)
            return value;
          });
            return value
          })
          // 更新图表
          this.updateRiskChart();
          this.updateRiskChart()
        }
      } catch (error) {
        console.error("获取项目风险趋势失败:", error);
        console.error('获取项目风险趋势失败:', error)
        // 设置默认值,确保图表可以正常显示
        this.riskTrendData.xAxisData = [];
        this.riskTrendData.seriesData = [];
        this.riskTrendData.xAxisData = []
        this.riskTrendData.seriesData = []
      }
    },
    // 更新风险趋势图表
    updateRiskChart() {
      const chartDom = document.getElementById("riskTrendChart");
    updateRiskChart () {
      const chartDom = document.getElementById('riskTrendChart')
      if (!chartDom) {
        console.warn("风险趋势图表DOM不存在,可能是权限限制导致该模块未渲染");
        return;
        console.warn('风险趋势图表DOM不存在,可能是权限限制导致该模块未渲染')
        return
      }
      const riskChart = echarts.getInstanceByDom(chartDom);
      const riskChart = echarts.getInstanceByDom(chartDom)
      if (!riskChart) {
        console.warn("风险趋势图表实例不存在");
        return;
        console.warn('风险趋势图表实例不存在')
        return
      }
      // 文本换行处理函数
      const formatLabel = (text, maxWidth = 4) => {
        if (!text) return "";
        const textStr = String(text);
        if (!text) return ''
        const textStr = String(text)
        // 如果文本长度超过最大宽度,进行换行
        if (textStr.length > maxWidth) {
          let result = "";
          let result = ''
          for (let i = 0; i < textStr.length; i += maxWidth) {
            result += textStr.substring(i, i + maxWidth) + "\n";
            result += textStr.substring(i, i + maxWidth) + '\n'
          }
          return result.trim();
          return result.trim()
        }
        return textStr;
      };
        return textStr
      }
      const self = this;
      const self = this
      const option = {
        tooltip: {
          trigger: "axis",
          trigger: 'axis',
          show: true,
          backgroundColor: "rgba(0, 0, 0, 0.7)",
          borderColor: "#32C5FF",
          backgroundColor: 'rgba(0, 0, 0, 0.7)',
          borderColor: '#32C5FF',
          borderWidth: 1,
          textStyle: {
            color: "#fff",
            fontSize: 12,
            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}`;
              const param = params[0]
              const levelText = self.valueToLevel(param.value)
              return `${param.name}<br/>${param.seriesName}: ${levelText}`
            }
            return "";
          },
            return ''
          }
        },
        legend: {
          show: false,
          show: false
        },
        grid: {
          top: "10%",
          bottom: "10%",
          left: "10%",
          right: "5%",
          containLabel: true,
          top: '10%',
          bottom: '10%',
          left: '10%',
          right: '5%',
          containLabel: true
        },
        xAxis: {
          data: this.riskTrendData.xAxisData,
          axisTick: { show: true, alignWithLabel: true },
          axisLine: {
            lineStyle: {
              color: "#666",
            },
              color: '#666'
            }
          },
          axisLabel: {
            color: "#fff",
            color: '#fff',
            fontSize: 12,
            interval: 0,
            rotate: 0,
            formatter: function (value) {
              return formatLabel(value, 7);
              return formatLabel(value, 7)
            },
            align: "center",
            verticalAlign: "top",
            align: 'center',
            verticalAlign: 'top',
            lineHeight: 14,
            width: 60,
            overflow: "break",
          },
            overflow: 'break'
          }
        },
        yAxis: {
          type: "value",
          type: 'value',
          min: 1,
          max: 3,
          interval: 1,
          splitLine: {
            show: true,
            lineStyle: {
              type: "dashed",
              color: "rgba(255, 255, 255, .2)",
            },
              type: 'dashed',
              color: 'rgba(255, 255, 255, .2)'
            }
          },
          axisLine: {
            show: false,
            show: false
          },
          axisTick: {
            show: false,
            show: false
          },
          axisLabel: {
            color: "#fff",
            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 "";
              if (value === 1) return 'C类'
              if (value === 2) return 'B类'
              if (value === 3) return 'A类'
              return ''
            },
            align: "right",
            verticalAlign: "middle",
            align: 'right',
            verticalAlign: 'middle',
            lineHeight: 14,
            width: 30,
          },
            width: 30
          }
        },
        series: [
          {
            name: "风险等级",
            type: "line",
            name: '风险等级',
            type: 'line',
            data: this.riskTrendData.seriesData,
            smooth: false,
            showSymbol: true,
            symbol: "circle",
            symbol: 'circle',
            symbolSize: 8,
            itemStyle: {
              color: "#32C5FF",
              borderColor: "#32C5FF",
              borderWidth: 2,
              color: '#32C5FF',
              borderColor: '#32C5FF',
              borderWidth: 2
            },
            lineStyle: {
              width: 5,
              color: {
                type: "linear",
                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)" },
                ],
              },
            },
          },
        ],
      };
                  { 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);
      riskChart.setOption(option)
    },
    // 初始化折线图
    initRiskChart() {
      const chartDom = document.getElementById("riskTrendChart");
    initRiskChart () {
      const chartDom = document.getElementById('riskTrendChart')
      if (!chartDom) {
        console.warn("风险趋势图表DOM不存在,可能是权限限制导致该模块未渲染");
        return;
        console.warn('风险趋势图表DOM不存在,可能是权限限制导致该模块未渲染')
        return
      }
      const riskChart = echarts.init(chartDom);
      const riskChart = echarts.init(chartDom)
      // 文本换行处理函数
      const formatLabel = (text, maxWidth = 4) => {
        if (!text) return "";
        const textStr = String(text);
        if (!text) return ''
        const textStr = String(text)
        // 如果文本长度超过最大宽度,进行换行
        if (textStr.length > maxWidth) {
          let result = "";
          let result = ''
          for (let i = 0; i < textStr.length; i += maxWidth) {
            result += textStr.substring(i, i + maxWidth) + "\n";
            result += textStr.substring(i, i + maxWidth) + '\n'
          }
          return result.trim();
          return result.trim()
        }
        return textStr;
      };
        return textStr
      }
      let option = {
        tooltip: {
          trigger: "axis",
          show: false,
          trigger: 'axis',
          show: false
        },
        legend: {
          show: false,
          show: false
        },
        grid: {
          top: "5%",
          bottom: "5%",
          left: "5%",
          right: "5%",
          containLabel: true,
          top: '5%',
          bottom: '5%',
          left: '5%',
          right: '5%',
          containLabel: true
        },
        xAxis: {
          data:
            this.riskTrendData.xAxisData.length > 0
              ? this.riskTrendData.xAxisData
              : [
                  "2025年1月检查",
                  "2025年2月检查",
                  "2025年3月检查",
                  "2025年4月检查",
                  "2025年5月检查",
                ],
                '2025年1月检查',
                '2025年2月检查',
                '2025年3月检查',
                '2025年4月检查',
                '2025年5月检查'
              ],
          axisTick: { show: true, alignWithLabel: true },
          axisLine: {
            lineStyle: {
              color: "#666",
            },
              color: '#666'
            }
          },
          axisLabel: {
            color: "#fff",
            color: '#fff',
            fontSize: 12,
            interval: 0, // 强制显示所有标签
            rotate: 0, // 不旋转
            formatter: function (value) {
              // X轴标签格式化,最多7个字符,超出换行
              return formatLabel(value, 7);
              return formatLabel(value, 7)
            },
            // 文本对齐方式
            align: "center",
            verticalAlign: "top",
            align: 'center',
            verticalAlign: 'top',
            lineHeight: 14,
            // 设置标签的最大宽度
            width: 60,
            overflow: "break", // 超出换行
          },
            overflow: 'break' // 超出换行
          }
        },
        yAxis: {
          type: "value",
          type: 'value',
          min: 1,
          max: 3,
          interval: 1,
          splitLine: {
            show: true,
            lineStyle: {
              type: "dashed", // 虚线
              color: "rgba(255, 255, 255, .2)", // 固定灰色
            },
              type: 'dashed', // 虚线
              color: 'rgba(255, 255, 255, .2)' // 固定灰色
            }
          },
          axisLine: {
            show: false,
            show: false
          },
          axisTick: {
            show: false,
            show: false
          },
          axisLabel: {
            color: "#fff",
            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 "";
              if (value === 1) return 'C类'
              if (value === 2) return 'B类'
              if (value === 3) return 'A类'
              return ''
            },
            // 文本对齐方式
            align: "right",
            verticalAlign: "middle",
            align: 'right',
            verticalAlign: 'middle',
            lineHeight: 14,
            // 设置标签的最大宽度
            width: 30,
          },
            width: 30
          }
        },
        series: [
          {
            name: "风险等级",
            type: "line",
            name: '风险等级',
            type: 'line',
            data:
              this.riskTrendData.seriesData.length > 0
                ? this.riskTrendData.seriesData
@@ -2270,35 +2270,35 @@
            lineStyle: {
              width: 5, // 线条宽度
              color: {
                type: "linear",
                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);
                  { 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)
    },
    // 处理风险趋势时间选择器点击
    handleRiskPickerClick() {
      this.$refs.riskDatePicker.focus();
    handleRiskPickerClick () {
      this.$refs.riskDatePicker.focus()
    },
    toRiskTrend() {
    toRiskTrend () {
      this.$router.push({
        path: "/subentry",
        query: { projectId: this.projectId, projectName: this.projectName },
      });
    },
  },
};
        path: '/subentry',
        query: { projectId: this.projectId, projectName: this.projectName }
      })
    }
  }
}
</script>
<style lang="less" scoped>
screen/src/view/projectList/index.vue
@@ -505,7 +505,7 @@
      projectOptions: [], // 项目下拉列表
      projectTypeOptions: [
        { label: '房建', value: 0 },
        { label: '市政', value: 1 },
        { label: '市政', value: 1 }
        // { label: '固定资产', value: 2 }
      ], // 项目类型下拉列表
      riskLevelOptions: [