| | |
| | | " |
| | | > |
| | | <div class="flex2 flex"> |
| | | <div class="label dept"> |
| | | 设备租赁单位 |
| | | </div> |
| | | <div class="label dept">设备租赁单位</div> |
| | | <div class="value" v-if="filteredRentalUnits.length >= 1"> |
| | | {{ |
| | | filteredRentalUnits[0][ |
| | | "工程起重机械租赁单位" |
| | | ] || "-" |
| | | }} |
| | | {{ filteredRentalUnits[0]["工程起重机械租赁单位"] || "-" }} |
| | | </div> |
| | | <el-tooltip |
| | | style="margin-left: 10px" |
| | | v-if="filteredRentalUnits.length > 1" |
| | | placement="top" |
| | | effect="dark" |
| | | > |
| | | <div slot="content" class="tooltip-content"> |
| | | <div |
| | | v-for="(item, index) in filteredRentalUnits" |
| | | :key="index" |
| | | class="tooltip-item" |
| | | > |
| | | <div class="tooltip-label">单位{{ index + 1 }}:</div> |
| | | <div class="tooltip-value"> |
| | | {{ item["工程起重机械租赁单位"] || "-" }} |
| | | </div> |
| | | <el-tooltip |
| | | style="margin-left: 10px" |
| | | v-if="filteredRentalUnits.length > 1" |
| | | placement="top" |
| | | effect="dark" |
| | | > |
| | | <div slot="content" class="tooltip-content"> |
| | | <div |
| | | v-for="(item, index) in filteredRentalUnits" |
| | | :key="index" |
| | | class="tooltip-item" |
| | | > |
| | | <div class="tooltip-label">单位{{ index + 1 }}:</div> |
| | | <div class="tooltip-value"> |
| | | {{ item["工程起重机械租赁单位"] || "-" }} |
| | | </div> |
| | | </div> |
| | | <span class="count-badge">{{ |
| | | filteredRentalUnits.length |
| | | }}</span> |
| | | </el-tooltip> |
| | | </div> |
| | | <span class="count-badge">{{ |
| | | filteredRentalUnits.length |
| | | }}</span> |
| | | </el-tooltip> |
| | | </div> |
| | | <div class="flex1 flex"> |
| | | <div class="label"> |
| | | 联系人 |
| | | </div> |
| | | <div class="label">联系人</div> |
| | | <div class="value" v-if="filteredContacts.length >= 1"> |
| | | {{ |
| | | filteredContacts[0]["联系人"] || "-" |
| | | }} |
| | | {{ filteredContacts[0]["联系人"] || "-" }} |
| | | </div> |
| | | <el-tooltip |
| | | style="margin-left: 10px" |
| | | v-if="filteredContacts.length > 1" |
| | | placement="top" |
| | | effect="dark" |
| | | > |
| | | <div slot="content" class="tooltip-content"> |
| | | <div |
| | | v-for="(item, index) in filteredContacts" |
| | | :key="index" |
| | | class="tooltip-item" |
| | | > |
| | | <div class="tooltip-label"> |
| | | 联系人{{ index + 1 }}: |
| | | </div> |
| | | <div class="tooltip-value"> |
| | | {{ item["联系人"] || "-" }} |
| | | </div> |
| | | <el-tooltip |
| | | style="margin-left: 10px" |
| | | v-if="filteredContacts.length > 1" |
| | | placement="top" |
| | | effect="dark" |
| | | > |
| | | <div slot="content" class="tooltip-content"> |
| | | <div |
| | | v-for="(item, index) in filteredContacts" |
| | | :key="index" |
| | | class="tooltip-item" |
| | | > |
| | | <div class="tooltip-label">联系人{{ index + 1 }}:</div> |
| | | <div class="tooltip-value"> |
| | | {{ item["联系人"] || "-" }} |
| | | </div> |
| | | </div> |
| | | <span class="count-badge">{{ |
| | | filteredContacts.length |
| | | }}</span> |
| | | </el-tooltip> |
| | | </div> |
| | | <span class="count-badge">{{ |
| | | filteredContacts.length |
| | | }}</span> |
| | | </el-tooltip> |
| | | </div> |
| | | </div> |
| | | <div class="info-item11"> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | <div |
| | | v-if="permissionProject.includes('12')" |
| | |
| | | </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, |
| | |
| | | getProjectSource, |
| | | getDangerTrend, |
| | | getProjectDangerousTrend, |
| | | getProjectLatestProgress |
| | | } from './service.js' |
| | | getProjectLatestProgress, |
| | | } from "./service.js"; |
| | | |
| | | import { transformRiskSourceData } from '@/utils/riskSourceMap.js' |
| | | import * as echarts from 'echarts' |
| | | 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, |
| | | }, // 危险源统计 |
| | | dangerCountTab: 'serious', |
| | | dangerCountTab: "serious", |
| | | dangerCountLoading: false, |
| | | dangerCountCache: { |
| | | all: null, |
| | | serious: null |
| | | serious: null, |
| | | }, |
| | | |
| | | deviceTypeList: [], // 设备类型列表 |
| | |
| | | |
| | | // pie数据 |
| | | legendData: [], |
| | | dateTxt: '', |
| | | dateTxt: "", |
| | | dangerTrendList: [], // 分项隐患趋势数据 |
| | | dangerSortType: 1, // 排序类型:1-隐患数量,2-趋势 |
| | | dangerSortDesc: true, // 是否倒序 |
| | |
| | | riskTrendData: { |
| | | xAxisData: [], |
| | | seriesData: [], |
| | | statusData: [] |
| | | statusData: [], |
| | | }, |
| | | // 9各分项隐患趋势 10隐患数量 11设备数量 12查看报告入口 13人员在岗率 14重大风险源 15 项目风险趋势 |
| | | permissionProject: |
| | | JSON.parse(sessionStorage.getItem('permissionProject')) || [], |
| | | JSON.parse(sessionStorage.getItem("permissionProject")) || [], |
| | | |
| | | // 项目进度相关 |
| | | progressVisible: false, // 进度弹窗显示状态 |
| | |
| | | progressMediaList: [], // 处理后的媒体列表(包含类型信息) |
| | | // 预览相关 |
| | | previewVisible: false, // 预览弹窗显示状态 |
| | | previewUrl: '', // 预览的URL |
| | | previewType: 'image', // 预览类型:'image' 或 'video' |
| | | clientName: '', // 客户名称 |
| | | clientLogo: '' // 客户logo |
| | | } |
| | | previewUrl: "", // 预览的URL |
| | | previewType: "image", // 预览类型:'image' 或 'video' |
| | | clientName: "", // 客户名称 |
| | | clientLogo: "", // 客户logo |
| | | }; |
| | | }, |
| | | computed: { |
| | | dangerCountTabName () { |
| | | return this.dangerCountTab === 'all' ? '所有隐患数' : '重大隐患数' |
| | | dangerCountTabName() { |
| | | return this.dangerCountTab === "all" ? "所有隐患数" : "重大隐患数"; |
| | | }, |
| | | // 处理风险源数据,添加合并单元格的逻辑 |
| | | |
| | | 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; |
| | | } |
| | | } |
| | | |
| | |
| | | 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) |
| | | 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 list |
| | | const item = JSON.parse(itemStr); |
| | | 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 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 name = item['联系人'] |
| | | if (!name) return |
| | | const key = String(name).trim() |
| | | if (!key) return |
| | | if (seen.has(key)) return |
| | | seen.add(key) |
| | | list.push({ '联系人': name }) |
| | | } catch (e) { |
| | | const item = JSON.parse(itemStr); |
| | | const name = item["联系人"]; |
| | | if (!name) return; |
| | | const key = String(name).trim(); |
| | | if (!key) return; |
| | | if (seen.has(key)) return; |
| | | seen.add(key); |
| | | list.push({ 联系人: name }); |
| | | } catch (e) {} |
| | | }); |
| | | |
| | | } |
| | | }) |
| | | |
| | | 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) |
| | | sessionStorage.setItem('clientId', res.clientId) |
| | | 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); |
| | | sessionStorage.setItem("clientId", res.clientId); |
| | | 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}/public/file/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); |
| | | // 可以在这里添加视频加载失败的处理逻辑 |
| | | }, |
| | | normalizeDangerCount (res) { |
| | | normalizeDangerCount(res) { |
| | | return { |
| | | serious: res && res.serious !== undefined ? res.serious : 0, |
| | | rectified: res && res.rectified !== undefined ? res.rectified : 0, |
| | | unRectified: res && res.unRectified !== undefined |
| | | ? res.unRectified |
| | | : 0 |
| | | } |
| | | unRectified: res && res.unRectified !== undefined ? res.unRectified : 0, |
| | | }; |
| | | }, |
| | | async loadDangerCount (tab = this.dangerCountTab, force = false) { |
| | | const cacheData = this.dangerCountCache[tab] |
| | | async loadDangerCount(tab = this.dangerCountTab, force = false) { |
| | | const cacheData = this.dangerCountCache[tab]; |
| | | if (!force && cacheData) { |
| | | this.dangerCount = { ...cacheData } |
| | | return |
| | | this.dangerCount = { ...cacheData }; |
| | | return; |
| | | } |
| | | |
| | | this.dangerCountLoading = true |
| | | this.dangerCountLoading = true; |
| | | try { |
| | | const requestApi = tab === 'all' ? getDangerCountAll : getDangerCount |
| | | const res = await requestApi(this.projectId) |
| | | const countData = this.normalizeDangerCount(res) |
| | | this.dangerCount = countData |
| | | this.dangerCountCache[tab] = { ...countData } |
| | | const requestApi = tab === "all" ? getDangerCountAll : getDangerCount; |
| | | const res = await requestApi(this.projectId); |
| | | const countData = this.normalizeDangerCount(res); |
| | | this.dangerCount = countData; |
| | | this.dangerCountCache[tab] = { ...countData }; |
| | | } catch (err) { |
| | | console.error( |
| | | `获取${tab === 'all' ? '所有隐患数量' : '重大隐患数量'}失败:`, |
| | | `获取${tab === "all" ? "所有隐患数量" : "重大隐患数量"}失败:`, |
| | | err |
| | | ) |
| | | this.dangerCount = this.normalizeDangerCount() |
| | | ); |
| | | this.dangerCount = this.normalizeDangerCount(); |
| | | } finally { |
| | | this.dangerCountLoading = false |
| | | this.dangerCountLoading = false; |
| | | } |
| | | }, |
| | | handleDangerTabChange (tab) { |
| | | if (this.dangerCountTab === tab) return |
| | | this.dangerCountTab = tab |
| | | this.loadDangerCount(tab) |
| | | handleDangerTabChange(tab) { |
| | | if (this.dangerCountTab === tab) return; |
| | | this.dangerCountTab = tab; |
| | | this.loadDangerCount(tab); |
| | | }, |
| | | // 获取项目详情 |
| | | getDetail () { |
| | | getDetail() { |
| | | this.dangerCountCache = { |
| | | all: null, |
| | | serious: null |
| | | } |
| | | serious: null, |
| | | }; |
| | | |
| | | // 设备类型 - 添加错误处理 |
| | | 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 = []; |
| | | }); |
| | | |
| | | this.loadDangerCount(this.dangerCountTab, true) |
| | | this.loadDangerCount(this.dangerCountTab, true); |
| | | |
| | | // 项目人员信息 - 添加错误处理 |
| | | |
| | | 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', |
| | | query: { |
| | | projectId: this.projectId, |
| | | projectName: this.projectName |
| | | } |
| | | }) |
| | | }, |
| | | // 跳转重大危险源 |
| | | toMajorRiskList () { |
| | | this.$router.push({ |
| | | path: '/majorRisk', |
| | | query: { |
| | | projectId: this.projectId, |
| | | projectName: this.projectName |
| | | } |
| | | }) |
| | | }, |
| | | toPlanOfAudit () { |
| | | this.$router.push({ |
| | | path: '/planOfAudit', |
| | | path: "/equipmentList", |
| | | query: { |
| | | projectId: this.projectId, |
| | | projectName: this.projectName, |
| | | type: 2 |
| | | } |
| | | }) |
| | | }, |
| | | }); |
| | | }, |
| | | setMapload () { |
| | | // 跳转重大危险源 |
| | | toMajorRiskList() { |
| | | this.$router.push({ |
| | | path: "/majorRisk", |
| | | query: { |
| | | projectId: this.projectId, |
| | | projectName: this.projectName, |
| | | }, |
| | | }); |
| | | }, |
| | | toPlanOfAudit() { |
| | | this.$router.push({ |
| | | path: "/planOfAudit", |
| | | query: { |
| | | projectId: this.projectId, |
| | | projectName: this.projectName, |
| | | type: 2, |
| | | }, |
| | | }); |
| | | }, |
| | | 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] |
| | | if ( |
| | | !isNaN(longitude) && |
| | | !isNaN(latitude) && |
| | | longitude !== 0 && |
| | | latitude !== 0 |
| | | ) { |
| | | 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') |
| | | ] |
| | | |
| | | const iconImage = iconImages[this.projectInfo.type] || iconImages[0] |
| | | require("../../assets/img/home/new-mark.png"), |
| | | require("../../assets/img/home/s4.png"), |
| | | require("../../assets/img/home/z4.png"), |
| | | ]; |
| | | // const iconImage = iconImages[this.projectInfo.type] || iconImages[0]; |
| | | const iconImage = 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) { |
| | |
| | | position: [lng, lat], |
| | | icon: new AMap.Icon({ |
| | | image: iconImage, |
| | | size: new AMap.Size(30, 30), |
| | | imageSize: new AMap.Size(30, 30) |
| | | size: new AMap.Size(60, 60), |
| | | imageSize: new AMap.Size(60, 60), |
| | | }), |
| | | 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]) |
| | | offset: new AMap.Pixel(-30, -30), |
| | | 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) 无经纬度时,使用地址进行地理编码 |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | false, |
| | | k, |
| | | prev.pieData.value |
| | | ) |
| | | prev.pieStatus.selected = false |
| | | ); |
| | | prev.pieStatus.selected = false; |
| | | } |
| | | } |
| | | |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | 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 |
| | | } |
| | | } |
| | | k: k, |
| | | }, |
| | | }; |
| | | |
| | | if (typeof pieData[i].itemStyle !== 'undefined') { |
| | | let itemStyle = {} |
| | | if (typeof pieData[i].itemStyle !== "undefined") { |
| | | let itemStyle = {}; |
| | | |
| | | typeof pieData[i].itemStyle.color !== 'undefined' |
| | | typeof pieData[i].itemStyle.color !== "undefined" |
| | | ? (itemStyle.color = pieData[i].itemStyle.color) |
| | | : null |
| | | typeof pieData[i].itemStyle.opacity !== 'undefined' |
| | | : null; |
| | | typeof pieData[i].itemStyle.opacity !== "undefined" |
| | | ? (itemStyle.opacity = pieData[i].itemStyle.opacity) |
| | | : null |
| | | : null; |
| | | |
| | | seriesItem.itemStyle = itemStyle |
| | | seriesItem.itemStyle = itemStyle; |
| | | } |
| | | series.push(seriesItem) |
| | | 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, |
| | |
| | | false, |
| | | k, |
| | | series[i].pieData.value |
| | | ) |
| | | ); |
| | | |
| | | startValue = endValue |
| | | startValue = endValue; |
| | | |
| | | legendData.push(series[i].name) |
| | | legendData.push(series[i].name); |
| | | } |
| | | // series.push({ |
| | | // name: 'mouseoutSeries', |
| | |
| | | // }, |
| | | // }, |
| | | // }); |
| | | 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 |
| | | }) |
| | | this.riskTrendData.statusData = res.map((item) => item.status) |
| | | return value; |
| | | }); |
| | | this.riskTrendData.statusData = res.map((item) => item.status); |
| | | |
| | | // 更新图表 |
| | | this.updateRiskChart() |
| | | this.updateRiskChart(); |
| | | } |
| | | } catch (error) { |
| | | console.error('获取项目风险趋势失败:', error) |
| | | console.error("获取项目风险趋势失败:", error); |
| | | // 设置默认值,确保图表可以正常显示 |
| | | this.riskTrendData.xAxisData = [] |
| | | this.riskTrendData.seriesData = [] |
| | | this.riskTrendData.statusData = [] |
| | | this.riskTrendData.xAxisData = []; |
| | | this.riskTrendData.seriesData = []; |
| | | this.riskTrendData.statusData = []; |
| | | } |
| | | }, |
| | | // 更新风险趋势图表 |
| | | 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: (value, index) => { |
| | | const status = |
| | | Array.isArray(this.riskTrendData.statusData) && |
| | | typeof index === 'number' |
| | | typeof index === "number" |
| | | ? this.riskTrendData.statusData[index] |
| | | : undefined |
| | | return Number(status) === 2 ? '#999' : '#fff' |
| | | : undefined; |
| | | return Number(status) === 2 ? "#999" : "#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: (value, index) => { |
| | | const status = |
| | | Array.isArray(this.riskTrendData.statusData) && |
| | | typeof index === 'number' |
| | | typeof index === "number" |
| | | ? this.riskTrendData.statusData[index] |
| | | : undefined |
| | | return Number(status) === 2 ? '#999' : '#fff' |
| | | : undefined; |
| | | return Number(status) === 2 ? "#999" : "#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 |
| | |
| | | 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> |
| | |
| | | } |
| | | |
| | | .mid-img { |
| | | |
| | | width: 90px; |
| | | height: 90px; |
| | | margin-right: 20px; |