| | |
| | | /> |
| | | <img |
| | | v-else |
| | | src="../../assets/img/home/assets.png" |
| | | src="../../assets/img/home/znull.png" |
| | | class="w--18 h--16 shrink0" |
| | | /> |
| | | <div :class="!categoryFilter.assets && 'gray-text'">资产</div> |
| | |
| | | class="el-icon-circle-close c-fff cursor-pointer" |
| | | v-show="projectId" |
| | | /> |
| | | <div class="btn">搜索</div> |
| | | <div class="btn" @click="handleSearch">搜索</div> |
| | | </div> |
| | | </div> |
| | | <div v-if="permissionClient.includes('2')" class="mt--33 relative"> |
| | |
| | | </TitleBox> |
| | | </div> |
| | | </div> |
| | | <!-- 自定义悬浮窗 --> |
| | | <div |
| | | v-if="tooltipVisible" |
| | | class="map-tooltip-wrapper" |
| | | :style="{ left: tooltipPos.left + 'px', top: tooltipPos.top + 'px' }" |
| | | > |
| | | <div class="custom-tooltip"> |
| | | <div v-if="tooltipLoading" class="loading"> |
| | | <i class="el-icon-loading"></i> 数据加载中... |
| | | </div> |
| | | <div v-else-if="tooltipError" class="error">数据加载失败</div> |
| | | <div v-else> |
| | | <div class="title">{{ tooltipData.name || "-" }}</div> |
| | | <div class="row"> |
| | | <span class="label">项目地址:</span> |
| | | <span class="value">{{ tooltipData.address || "-" }}</span> |
| | | </div> |
| | | <div class="flex gap--20"> |
| | | <div class="row"> |
| | | <span class="label">施工单位:</span> |
| | | <span class="value"> |
| | | {{ tooltipData.constructionCompanyInfo.name || "-" }} |
| | | </span> |
| | | </div> |
| | | <div class="row"> |
| | | <span class="label">项目经理:</span> |
| | | <span class="value"> |
| | | {{ tooltipData.constructionCompanyInfo.leader || "-" }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | <div class="flex gap--20"> |
| | | <div class="row"> |
| | | <span class="label">监理单位:</span> |
| | | <span class="value"> |
| | | {{ tooltipData.supervisorCompanyInfo.name || "-" }} |
| | | </span> |
| | | </div> |
| | | <div class="row"> |
| | | <span class="label">总监:</span> |
| | | <span class="value"> |
| | | {{ tooltipData.supervisorCompanyInfo.leader || "-" }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | <div class="flex gap--20"> |
| | | <div class="row"> |
| | | <span class="label">建设单位:</span> |
| | | <span class="value"> |
| | | {{ tooltipData.developmentCompanyInfo.name || "-" }} |
| | | </span> |
| | | </div> |
| | | <div class="row"> |
| | | <span class="label">项目负责人:</span> |
| | | <span class="value"> |
| | | {{ tooltipData.developmentCompanyInfo.leader || "-" }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | <div class="flex gap--20"> |
| | | <div class="row"> |
| | | <span class="label">建设单位:</span> |
| | | <span class="value"> |
| | | {{ tooltipData.developmentCompanyInfo.name || "-" }} |
| | | </span> |
| | | </div> |
| | | <div class="row"> |
| | | <span class="label">项目负责人:</span> |
| | | <span class="value"> |
| | | {{ tooltipData.developmentCompanyInfo.leader || "-" }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | <div |
| | | class="flex gap--20" |
| | | v-for="(item, index) in tooltipData.properties" |
| | | :key="index" |
| | | > |
| | | <div class="row"> |
| | | <span class="label">设备租赁单位:</span> |
| | | <span class="value"> |
| | | {{ JSON.parse(item)["工程起重机械租赁单位"] || "-" }} |
| | | </span> |
| | | </div> |
| | | <div class="row"> |
| | | <span class="label">联系人:</span> |
| | | <span class="value"> |
| | | {{ JSON.parse(item)["联系电话"] || "-" }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | <div |
| | | class="flex flex-warp gap--20" |
| | | v-for="(item, index) in tooltipData.deviceCountVos" |
| | | :key="index" |
| | | > |
| | | <div class="row"> |
| | | <span class="label">{{ item.name }}:</span> |
| | | <span class="value"> |
| | | {{ item.count || 0 }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import z1 from "../../assets/img/home/assets1.png"; |
| | | import z2 from "../../assets/img/home/assets2.png"; |
| | | import z3 from "../../assets/img/home/assets3.png"; |
| | | import s4 from "../../assets/img/home/s4.png"; |
| | | import f4 from "../../assets/img/home/f4.png"; |
| | | import z4 from "../../assets/img/home/z4.png"; |
| | | import { |
| | | getStatistics, |
| | | getStoryList, |
| | |
| | | workListData: [], |
| | | VUE_APP_IMAGE_BASE_URL: process.env.VUE_APP_IMAGE_BASE_URL, |
| | | projectList: [], |
| | | tooltipVisible: false, |
| | | tooltipPos: { left: 0, top: 0 }, |
| | | tooltipData: {}, |
| | | tooltipLoading: false, |
| | | tooltipError: false, |
| | | }; |
| | | }, |
| | | watch: { |
| | |
| | | mounted() { |
| | | this.$nextTick(() => { |
| | | this.scrollContainer = this.$refs.scrollContainer; |
| | | this.initScrollData(); |
| | | }); |
| | | }, |
| | | beforeDestroy() { |
| | |
| | | time: time, |
| | | }).then((res) => { |
| | | this.scrollData = res; |
| | | if (this.screenData.length > 4) { |
| | | this.initScrollData(); |
| | | } |
| | | }); |
| | | }, |
| | | getScreenData() { |
| | |
| | | handleClick() { |
| | | this.$refs.datePicker.focus(); |
| | | }, |
| | | handleSearch() { |
| | | if (!this.projectId) { |
| | | return; |
| | | } |
| | | const target = this.projectList.find( |
| | | (item) => item.id === this.projectId |
| | | ); |
| | | if (!target) return; |
| | | |
| | | let position = null; |
| | | if (target.addressInfo) { |
| | | try { |
| | | const info = |
| | | typeof target.addressInfo === "string" |
| | | ? JSON.parse(target.addressInfo) |
| | | : target.addressInfo; |
| | | |
| | | if (info.gps && info.gps.length) { |
| | | position = [Number(info.gps[0]), Number(info.gps[1])]; |
| | | } |
| | | } catch (e) { |
| | | console.error("解析项目地址信息失败", e); |
| | | } |
| | | } |
| | | |
| | | if (position) { |
| | | // 定位地图 |
| | | this.mapView.setZoomAndCenter(15, position); |
| | | |
| | | // 显示悬浮窗 |
| | | this.showTooltip(null, { |
| | | id: target.id, |
| | | position: position, |
| | | }); |
| | | } else { |
| | | this.$message.warning("该项目暂无位置信息"); |
| | | } |
| | | }, |
| | | // 初始化滚动数据 |
| | | initScrollData() { |
| | | // 创建示例数据,模拟真实的事故简讯数据 |
| | | const sampleData = [ |
| | | { |
| | | id: 1, |
| | | type: "塔吊倒塌", |
| | | title: "秦皇岛市钢铁厂发生塔吊倒塌事故", |
| | | location: "河北省秦皇岛市昌黎县", |
| | | date: "2025年11月10日", |
| | | casualties: "造成 1 人死亡", |
| | | }, |
| | | { |
| | | id: 2, |
| | | type: "脚手架坍塌", |
| | | title: "广州市某工地脚手架坍塌事故", |
| | | location: "广东省广州市白云区", |
| | | date: "2025年11月9日", |
| | | casualties: "造成 2 人受伤", |
| | | }, |
| | | { |
| | | id: 3, |
| | | type: "高处坠落", |
| | | title: "深圳市某建筑工地工人高处坠落", |
| | | location: "广东省深圳市南山区", |
| | | date: "2025年11月8日", |
| | | casualties: "造成 1 人死亡,1 人重伤", |
| | | }, |
| | | { |
| | | id: 4, |
| | | type: "物体打击", |
| | | title: "成都市某工地发生物体打击事故", |
| | | location: "四川省成都市武侯区", |
| | | date: "2025年11月7日", |
| | | casualties: "造成 1 人轻伤", |
| | | }, |
| | | { |
| | | id: 5, |
| | | type: "触电事故", |
| | | title: "杭州市某工地发生触电事故", |
| | | location: "浙江省杭州市西湖区", |
| | | date: "2025年11月6日", |
| | | casualties: "造成 1 人死亡", |
| | | }, |
| | | { |
| | | id: 6, |
| | | type: "机械伤害", |
| | | title: "南京市某工地发生机械伤害事故", |
| | | location: "江苏省南京市江宁区", |
| | | date: "2025年11月5日", |
| | | casualties: "造成 1 人重伤", |
| | | }, |
| | | { |
| | | id: 7, |
| | | type: "坍塌事故", |
| | | title: "武汉市某工地发生局部坍塌事故", |
| | | location: "湖北省武汉市汉阳区", |
| | | date: "2025年11月4日", |
| | | casualties: "造成 1 人死亡,2 人受伤", |
| | | }, |
| | | ]; |
| | | this.stopAutoScroll(); |
| | | if (!this.scrollData || this.scrollData.length === 0) return; |
| | | |
| | | // 为了实现无缝滚动,复制2份数据 |
| | | this.scrollData = [...sampleData, ...sampleData]; |
| | | this.scrollData = [...this.scrollData, ...this.scrollData]; |
| | | |
| | | // 开始滚动 |
| | | this.startAutoScroll(); |
| | | this.$nextTick(() => { |
| | | this.startAutoScroll(); |
| | | }); |
| | | }, |
| | | |
| | | // 开始自动滚动 |
| | | startAutoScroll() { |
| | | this.stopAutoScroll(); |
| | | |
| | | if (!this.scrollContainer) return; |
| | | if (!this.scrollContainer || !this.scrollData.length) return; |
| | | |
| | | const originalDataLength = this.scrollData.length / 2; |
| | | const itemHeight = 102; // 每个项目高度 |
| | | |
| | | // 动态计算高度 |
| | | let firstHalfHeight = 0; |
| | | const wrapper = this.scrollContainer.children[0]; |
| | | if (wrapper && wrapper.children.length) { |
| | | for (let i = 0; i < originalDataLength; i++) { |
| | | if (wrapper.children[i]) { |
| | | firstHalfHeight += wrapper.children[i].offsetHeight; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 兜底 |
| | | if (firstHalfHeight === 0) firstHalfHeight = originalDataLength * 102; |
| | | |
| | | // 判断是否需要跳转的标志 |
| | | let shouldJump = false; |
| | |
| | | let currentScrollTop = this.scrollContainer.scrollTop; |
| | | |
| | | // 当滚动到第二份的末尾时 |
| | | if (currentScrollTop >= originalDataLength * itemHeight) { |
| | | if (currentScrollTop >= firstHalfHeight) { |
| | | // 计算当前滚动位置相对于第二份数据开始的偏移量 |
| | | const offset = currentScrollTop - originalDataLength * itemHeight; |
| | | const offset = currentScrollTop - firstHalfHeight; |
| | | // 直接跳到第一份数据相同偏移量的位置 |
| | | this.scrollContainer.scrollTop = offset; |
| | | // 设置标志表示刚刚进行过跳转 |
| | |
| | | center: [104.065617, 30.656952], // 初始化地图中心点 |
| | | mapStyle: "amap://styles/darkblue", |
| | | }); |
| | | // this.infoWindow = new AMap.InfoWindow({ |
| | | // isCustom: true, |
| | | // offset: new AMap.Pixel(0, -30), |
| | | // }); |
| | | this.initCategoryMarkers(AMap); |
| | | }) |
| | | .catch((e) => { |
| | |
| | | return; |
| | | } |
| | | const points = this.screenData.projectMapPoints.map((item) => { |
| | | let category; |
| | | if (this.activeTab === 2) { |
| | | category = "assets"; |
| | | } else { |
| | | category = ["house", "municipal", "assets"][item.projectType]; |
| | | } |
| | | return { |
| | | ...item, |
| | | position: [Number(item.longitude), Number(item.latitude)], |
| | | category: ["house", "municipal", "assets"][item.projectType], |
| | | category: category, |
| | | level: item.category ? item.category.splice(0, 1) : "", |
| | | }; |
| | | }); |
| | |
| | | } |
| | | let image = null; |
| | | let level = item.level; |
| | | if (!level) { |
| | | image = z3; |
| | | } else if (item.category === "municipal") { |
| | | if (item.category === "municipal") { |
| | | if (item.level === "A") { |
| | | image = s1; |
| | | } else if (item.level === "B") { |
| | | image = s2; |
| | | } else if (item.level === "C") { |
| | | image = s3; |
| | | } else { |
| | | image = s4; |
| | | } |
| | | } else if (item.category === "house") { |
| | | if (item.level === "A") { |
| | |
| | | image = f2; |
| | | } else if (item.level === "C") { |
| | | image = f3; |
| | | } else { |
| | | image = f4; |
| | | } |
| | | } else if (item.category === "assets") { |
| | | if (item.level === "A") { |
| | |
| | | image = z2; |
| | | } else if (item.level === "C") { |
| | | image = z3; |
| | | } else { |
| | | image = z4; |
| | | } |
| | | } |
| | | if (!image) { |
| | |
| | | level: level, |
| | | }, |
| | | }); |
| | | marker.on("click", (item) => { |
| | | console.log(item); |
| | | marker.on("mouseover", (e) => { |
| | | this.showTooltip(e, item); |
| | | }); |
| | | marker.on("mouseout", () => { |
| | | this.hideTooltip(); |
| | | }); |
| | | return marker; |
| | | }, |
| | |
| | | } |
| | | this.levelFilter[level] = !this.levelFilter[level]; |
| | | this.updateMarkerVisibility(); |
| | | }, |
| | | showTooltip(e, item) { |
| | | if (!this.mapView) return; |
| | | |
| | | const pixel = this.mapView.lngLatToContainer(item.position); |
| | | this.tooltipPos = { left: pixel.x, top: pixel.y }; |
| | | this.tooltipVisible = true; |
| | | this.tooltipLoading = true; |
| | | this.tooltipError = false; |
| | | this.tooltipData = {}; |
| | | |
| | | getprojectTooltip(item.projectId) |
| | | .then((res) => { |
| | | let address = "-"; |
| | | let constructionCompanyInfo = "-"; |
| | | let supervisorCompanyInfo = "-"; |
| | | let developmentCompanyInfo = "-"; |
| | | |
| | | try { |
| | | if (res.addressInfo) { |
| | | const addressInfo = JSON.parse(res.addressInfo); |
| | | address = |
| | | (addressInfo.province || "") + |
| | | (addressInfo.city || "") + |
| | | (addressInfo.district || "") + |
| | | (addressInfo.detail || ""); |
| | | } |
| | | if (res.constructionCompanyInfo) { |
| | | constructionCompanyInfo = JSON.parse(res.constructionCompanyInfo); |
| | | } |
| | | if (res.supervisorCompanyInfo) { |
| | | supervisorCompanyInfo = JSON.parse(res.supervisorCompanyInfo); |
| | | } |
| | | if (res.developmentCompanyInfo) { |
| | | developmentCompanyInfo = JSON.parse(res.developmentCompanyInfo); |
| | | } |
| | | } catch (error) { |
| | | console.error("Parse error", error); |
| | | } |
| | | |
| | | this.tooltipData = { |
| | | ...res, |
| | | address, |
| | | constructionCompanyInfo, |
| | | supervisorCompanyInfo, |
| | | developmentCompanyInfo, |
| | | }; |
| | | this.tooltipLoading = false; |
| | | }) |
| | | .catch((err) => { |
| | | this.tooltipLoading = false; |
| | | this.tooltipError = true; |
| | | }); |
| | | }, |
| | | hideTooltip() { |
| | | this.tooltipVisible = false; |
| | | }, |
| | | }, |
| | | }; |
| | |
| | | } |
| | | } |
| | | </style> |
| | | |
| | | <style lang="less"> |
| | | .map-tooltip-wrapper { |
| | | position: absolute; |
| | | z-index: 9999; |
| | | pointer-events: none; |
| | | transform: translate(-50%, -50%); |
| | | margin-top: -40px; |
| | | } |
| | | |
| | | .custom-tooltip { |
| | | position: relative; |
| | | background: rgba(0, 22, 53, 0.9); |
| | | border: 1px solid #204fb3; |
| | | border-radius: 4px; |
| | | padding: 10px; |
| | | color: #fff; |
| | | min-width: 250px; |
| | | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); |
| | | |
| | | &::after { |
| | | content: ""; |
| | | position: absolute; |
| | | bottom: -6px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | border-left: 6px solid transparent; |
| | | border-right: 6px solid transparent; |
| | | border-top: 6px solid #204fb3; |
| | | } |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | bottom: -5px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | border-left: 6px solid transparent; |
| | | border-right: 6px solid transparent; |
| | | border-top: 6px solid rgba(0, 22, 53, 0.9); |
| | | z-index: 1; |
| | | } |
| | | |
| | | .title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | margin-bottom: 8px; |
| | | color: #fff; |
| | | } |
| | | |
| | | .row { |
| | | display: flex; |
| | | font-size: 12px; |
| | | line-height: 1.5; |
| | | margin-bottom: 4px; |
| | | flex-shrink: 0; |
| | | |
| | | .label { |
| | | color: rgba(255, 255, 255, 0.7); |
| | | min-width: 70px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .value { |
| | | color: #fff; |
| | | flex: 1; |
| | | } |
| | | } |
| | | |
| | | .loading { |
| | | text-align: center; |
| | | padding: 10px; |
| | | color: #00ffd2; |
| | | } |
| | | |
| | | .error { |
| | | color: #ff4d4f; |
| | | text-align: center; |
| | | padding: 10px; |
| | | } |
| | | } |
| | | </style> |