| | |
| | | <HeaderBox /> |
| | | <div |
| | | id="container" |
| | | style="width: 1920px; height: 995px; background-color: #001635" |
| | | style=" |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | z-index: 0; |
| | | width: 100vw; |
| | | height: 100vh; |
| | | background-color: #001635; |
| | | " |
| | | ></div> |
| | | <!-- 类型切换tab --> |
| | | <div |
| | |
| | | > |
| | | <el-carousel-item v-for="item in workListData" :key="item.id"> |
| | | <div @click="toWorkDetail(item)" class="cursor-pointer"> |
| | | <img :src="VUE_APP_IMAGE_BASE_URL + item.img.split(',')[0]" class="w100 h--290" /> |
| | | <img |
| | | :src="VUE_APP_IMAGE_BASE_URL + item.img.split(',')[0]" |
| | | class="w100 h--290" |
| | | /> |
| | | <div |
| | | class="fs--18 lh--24 SHSBold py--10 pl--20 pr--14 c-fff" |
| | | > |
| | |
| | | searchTabs: ["本周", "本月", "本季度", "本年"], |
| | | activeTabs: "本周", |
| | | workListData: [], |
| | | VUE_APP_IMAGE_BASE_URL: process.env.VUE_APP_IMAGE_BASE_URL + "/project/download/", |
| | | VUE_APP_IMAGE_BASE_URL: |
| | | process.env.VUE_APP_IMAGE_BASE_URL + "/project/download/", |
| | | projectList: [], |
| | | tooltipVisible: false, |
| | | tooltipPos: { left: 0, top: 0 }, |
| | | tooltipData: {}, |
| | | tooltipLoading: false, |
| | | tooltipError: false, |
| | | boundaryPolygons: [], |
| | | }; |
| | | }, |
| | | watch: { |
| | |
| | | } else { |
| | | this.dateTxt = ""; |
| | | } |
| | | this.$nextTick(()=>{ |
| | | this.$nextTick(() => { |
| | | this.getScreenData(); |
| | | }) |
| | | }); |
| | | }, |
| | | immediate: true, |
| | | }, |
| | |
| | | this.mapPromise = AMapLoader.load({ |
| | | key: "232929db1cb31f26d42b3645a7456154", |
| | | version: "2.0", |
| | | plugins: ["AMap.DistrictSearch", "AMap.Geocoder"], |
| | | AMapUI: { |
| | | version: "1.1", |
| | | plugins: ["geo/DistrictExplorer"], |
| | | }, |
| | | }); |
| | | this.getStoryData(); |
| | | getWorkPresentationList({ |
| | |
| | | beforeDestroy() { |
| | | localStorage.setItem("activeTab", this.activeTab); |
| | | this.stopAutoScroll(); |
| | | if (this._mapResizeHandler) { |
| | | window.removeEventListener("resize", this._mapResizeHandler); |
| | | this._mapResizeHandler = null; |
| | | } |
| | | }, |
| | | methods: { |
| | | handleTabClick(tab) { |
| | |
| | | }, |
| | | toPorjectList() { |
| | | const query = { |
| | | projectCategory: ["ongoing", "fixed"][this.activeTab - 1] |
| | | projectCategory: ["ongoing", "fixed"][this.activeTab - 1], |
| | | }; |
| | | |
| | | // 如果有选择日期范围,则传递开始和结束时间 |
| | | if (this.dateRange && this.dateRange.length === 2) { |
| | | query.startTime = moment(this.dateRange[0]).format("YYYY-MM-DD HH:mm:ss"); |
| | | query.startTime = moment(this.dateRange[0]).format( |
| | | "YYYY-MM-DD HH:mm:ss" |
| | | ); |
| | | query.endTime = moment(this.dateRange[1]).format("YYYY-MM-DD HH:mm:ss"); |
| | | } |
| | | |
| | | this.$router.push({ |
| | | path: '/projectList', |
| | | query: query |
| | | path: "/projectList", |
| | | query: query, |
| | | }); |
| | | }, |
| | | toEquipmentList() { |
| | |
| | | |
| | | // 如果有选择日期范围,则传递开始和结束时间 |
| | | if (this.dateRange && this.dateRange.length === 2) { |
| | | query.startTime = moment(this.dateRange[0]).format("YYYY-MM-DD HH:mm:ss"); |
| | | query.startTime = moment(this.dateRange[0]).format( |
| | | "YYYY-MM-DD HH:mm:ss" |
| | | ); |
| | | query.endTime = moment(this.dateRange[1]).format("YYYY-MM-DD HH:mm:ss"); |
| | | } |
| | | |
| | | this.$router.push({ |
| | | path: '/equipmentList', |
| | | query: query |
| | | path: "/equipmentList", |
| | | query: query, |
| | | }); |
| | | }, |
| | | toHiddenDanger() { |
| | | const query = { |
| | | projectCategory: ["ongoing", "fixed"][this.activeTab - 1] |
| | | projectCategory: ["ongoing", "fixed"][this.activeTab - 1], |
| | | }; |
| | | |
| | | // 如果有选择日期范围,则传递开始和结束时间 |
| | | if (this.dateRange && this.dateRange.length === 2) { |
| | | query.startTime = moment(this.dateRange[0]).format("YYYY-MM-DD HH:mm:ss"); |
| | | query.startTime = moment(this.dateRange[0]).format( |
| | | "YYYY-MM-DD HH:mm:ss" |
| | | ); |
| | | query.endTime = moment(this.dateRange[1]).format("YYYY-MM-DD HH:mm:ss"); |
| | | } |
| | | |
| | | this.$router.push({ |
| | | path: '/hiddenDanger', |
| | | query: query |
| | | path: "/hiddenDanger", |
| | | query: query, |
| | | }); |
| | | }, |
| | | // 切换活动索引 |
| | |
| | | this.mapPromise = AMapLoader.load({ |
| | | key: "232929db1cb31f26d42b3645a7456154", |
| | | version: "2.0", |
| | | plugins: ["AMap.DistrictSearch", "AMap.Geocoder"], |
| | | AMapUI: { |
| | | version: "1.1", |
| | | plugins: ["geo/DistrictExplorer"], |
| | | }, |
| | | }); |
| | | } |
| | | this.mapPromise |
| | |
| | | center: [104.065617, 30.656952], // 初始化地图中心点 |
| | | mapStyle: "amap://styles/darkblue", |
| | | }); |
| | | this.mapView.resize(); |
| | | this._mapResizeHandler = () => { |
| | | if (this.mapView) { |
| | | this.mapView.resize(); |
| | | } |
| | | }; |
| | | window.addEventListener("resize", this._mapResizeHandler); |
| | | // this.infoWindow = new AMap.InfoWindow({ |
| | | // isCustom: true, |
| | | // offset: new AMap.Pixel(0, -30), |
| | | // }); |
| | | this.initCategoryMarkers(AMap); |
| | | this.renderAllBoundaries(); |
| | | }) |
| | | .catch((e) => { |
| | | console.error(e); //加载错误提示 |
| | |
| | | hideTooltip() { |
| | | this.tooltipVisible = false; |
| | | }, |
| | | clearBoundary() { |
| | | if (this.boundaryPolygons.length) { |
| | | this.mapView.remove(this.boundaryPolygons); |
| | | this.boundaryPolygons = []; |
| | | } |
| | | }, |
| | | async renderAllBoundaries() { |
| | | this.clearBoundary(); |
| | | if (!this.mapView || !this.screenData.projectMapPoints) return; |
| | | |
| | | // 确保 AMapUI 已加载 |
| | | if (!window.AMapUI) { |
| | | console.warn("AMapUI not loaded"); |
| | | return; |
| | | } |
| | | |
| | | const provinceSet = new Set(); |
| | | const districtSet = new Set(); |
| | | const citySet = new Set(); // 显式收集有数据的城市 |
| | | const pointsToGeocode = []; |
| | | |
| | | // 1. 收集数据 |
| | | this.screenData.projectMapPoints.forEach((point) => { |
| | | let hasData = false; |
| | | if (point.addressInfo) { |
| | | try { |
| | | const info = JSON.parse(point.addressInfo); |
| | | if (info.province) { |
| | | provinceSet.add(info.province); |
| | | hasData = true; |
| | | } |
| | | if (info.district) { |
| | | districtSet.add(info.district); |
| | | hasData = true; |
| | | } |
| | | // 如果只有市没有区,或者需要单独渲染市 |
| | | if (info.city) { |
| | | citySet.add(info.city); |
| | | hasData = true; |
| | | } |
| | | } catch (e) { |
| | | // ignore |
| | | } |
| | | } |
| | | if (!hasData) { |
| | | pointsToGeocode.push([point.longitude, point.latitude]); |
| | | } |
| | | }); |
| | | |
| | | // 2. 逆地理编码补充缺失数据 |
| | | if (pointsToGeocode.length > 0) { |
| | | const geocoder = new AMap.Geocoder(); |
| | | const batchSize = 20; |
| | | const batches = []; |
| | | for (let i = 0; i < pointsToGeocode.length; i += batchSize) { |
| | | batches.push(pointsToGeocode.slice(i, i + batchSize)); |
| | | } |
| | | |
| | | await Promise.all( |
| | | batches.map((batch) => { |
| | | return new Promise((resolve) => { |
| | | geocoder.getAddress(batch, (status, result) => { |
| | | if (status === "complete" && result.regeocodes) { |
| | | result.regeocodes.forEach((geo) => { |
| | | if (geo.addressComponent) { |
| | | const { district, city, province } = geo.addressComponent; |
| | | if (province) provinceSet.add(province); |
| | | if (city) citySet.add(city); |
| | | if (district) districtSet.add(district); |
| | | } |
| | | }); |
| | | } |
| | | resolve(); |
| | | }); |
| | | }); |
| | | }) |
| | | ); |
| | | } |
| | | |
| | | // 初始化 DistrictExplorer |
| | | let districtExplorer = null; |
| | | try { |
| | | districtExplorer = new AMapUI.DistrictExplorer({ |
| | | map: this.mapView, |
| | | eventSupport: false, // 我们自己绘制,不需要它的事件 |
| | | }); |
| | | } catch (e) { |
| | | console.error("Init DistrictExplorer failed", e); |
| | | return; |
| | | } |
| | | |
| | | // 辅助函数:根据名称查找 adcode |
| | | const resolveAdcode = (name, level) => { |
| | | return new Promise((resolve) => { |
| | | const search = new AMap.DistrictSearch({ |
| | | subdistrict: 1, // 获取下一级以便后续逻辑使用(如省查市) |
| | | extensions: "base", // 这里只需要 adcode,不需要详细边界 |
| | | level: level, |
| | | }); |
| | | search.search(name, (status, result) => { |
| | | if (status === "complete" && result.districtList.length > 0) { |
| | | resolve(result.districtList[0]); |
| | | } else { |
| | | resolve(null); |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 辅助函数:使用 DistrictExplorer 加载并绘制区域 |
| | | const renderAreaNode = ( |
| | | adcode, |
| | | style, |
| | | level, |
| | | isActive = false, |
| | | isHoverable = false |
| | | ) => { |
| | | return new Promise((resolve) => { |
| | | districtExplorer.loadAreaNode(adcode, (error, areaNode) => { |
| | | if (error || !areaNode) { |
| | | resolve(); |
| | | return; |
| | | } |
| | | |
| | | // 获取父级区域的 GeoJSON (ParentFeature) |
| | | // 这包含了正确的孔洞信息 |
| | | const feature = areaNode.getParentFeature(); |
| | | if (!feature || !feature.geometry) { |
| | | resolve(); |
| | | return; |
| | | } |
| | | |
| | | const geometry = feature.geometry; |
| | | const types = |
| | | geometry.type === "MultiPolygon" |
| | | ? geometry.coordinates |
| | | : [geometry.coordinates]; |
| | | |
| | | // 遍历所有多边形(处理 MultiPolygon) |
| | | types.forEach((coords) => { |
| | | // GeoJSON 的 coordinates 是 [lng, lat] 数组 |
| | | // AMap.Polygon 接受 path,格式一致 |
| | | // 注意:GeoJSON Polygon 是 [OuterRing, Hole1, Hole2...] |
| | | // AMap.Polygon 同样支持这种格式 |
| | | |
| | | const polygon = new AMap.Polygon({ |
| | | path: coords, // 直接使用 GeoJSON 的坐标数组 |
| | | strokeColor: style.strokeColor, |
| | | strokeWeight: 2, |
| | | fillColor: style.fillColor || "transparent", |
| | | fillOpacity: |
| | | style.fillOpacity !== undefined ? style.fillOpacity : 0.2, |
| | | zIndex: style.zIndex, |
| | | bubble: true, |
| | | cursor: "default", |
| | | extData: { level, isActive }, |
| | | }); |
| | | |
| | | // 添加 hover 事件 |
| | | if (isHoverable) { |
| | | polygon.on("mouseover", () => { |
| | | polygon.setOptions({ |
| | | fillColor: "#00ffd2", |
| | | fillOpacity: 0.2, |
| | | }); |
| | | }); |
| | | polygon.on("mouseout", () => { |
| | | polygon.setOptions({ |
| | | fillColor: "transparent", |
| | | fillOpacity: 0, |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | this.mapView.add(polygon); |
| | | this.boundaryPolygons.push(polygon); |
| | | }); |
| | | |
| | | resolve(); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 3. 处理省份:渲染省界,并收集其下属的所有城市 adcode |
| | | const cityCodesFromProvinces = new Set(); |
| | | const cityAdcodeToNameMap = new Map(); // 辅助Map,用于判断是否在citySet中 |
| | | |
| | | const provincePromises = Array.from(provinceSet).map( |
| | | async (provinceName) => { |
| | | const result = await resolveAdcode(provinceName, "province"); |
| | | if (result) { |
| | | // 渲染省 - 无填充 |
| | | renderAreaNode( |
| | | result.adcode, |
| | | { |
| | | strokeColor: "#5470C6", |
| | | fillColor: "", |
| | | fillOpacity: 0, |
| | | zIndex: 8, |
| | | }, |
| | | "province", |
| | | false |
| | | ); |
| | | |
| | | // 收集该省下的所有市(用于渲染市界) |
| | | if (result.districtList && result.districtList.length) { |
| | | result.districtList.forEach((city) => { |
| | | cityCodesFromProvinces.add(city.adcode); |
| | | cityAdcodeToNameMap.set(city.adcode, city.name); |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | ); |
| | | |
| | | await Promise.all(provincePromises); |
| | | |
| | | // 4. 渲染市 |
| | | // 合并 "省份下属的市" 和 "直接出现在数据中的市" |
| | | const allCityAdcodes = new Set([...cityCodesFromProvinces]); |
| | | |
| | | // 处理直接出现的市名称 |
| | | const cityResolutionPromises = Array.from(citySet).map( |
| | | async (cityName) => { |
| | | const result = await resolveAdcode(cityName, "city"); |
| | | if (result) { |
| | | allCityAdcodes.add(result.adcode); |
| | | cityAdcodeToNameMap.set(result.adcode, cityName); |
| | | } |
| | | } |
| | | ); |
| | | await Promise.all(cityResolutionPromises); |
| | | |
| | | const cityRenderPromises = Array.from(allCityAdcodes).map((adcode) => { |
| | | // 渲染所有市 - 无填充 (仅轮廓) |
| | | // 注意:isActive设为false,因为我们只希望聚焦在具体的区或标点上 |
| | | return renderAreaNode( |
| | | adcode, |
| | | { strokeColor: "#FAC858", fillColor: "", fillOpacity: 0, zIndex: 9 }, |
| | | "city", |
| | | false |
| | | ); |
| | | }); |
| | | |
| | | await Promise.all(cityRenderPromises); |
| | | |
| | | // 5. 渲染区 |
| | | // 策略: |
| | | // 1. 遍历 citySet (有数据的市),渲染其下属的所有区。 |
| | | // - 如果区在 districtSet 中 (有数据):高亮填充 (isActive=true) |
| | | // - 如果区不在 districtSet 中 (无数据):透明 + Hover效果 (isActive=false) |
| | | // 2. 遍历 districtSet,处理可能漏掉的区 (比如没有对应的city信息的孤立区数据) |
| | | |
| | | const renderedDistrictAdcodes = new Set(); |
| | | |
| | | const cityDistrictPromises = Array.from(citySet).map(async (cityName) => { |
| | | const result = await resolveAdcode(cityName, "city"); |
| | | if (result && result.districtList) { |
| | | const subDistricts = result.districtList; |
| | | const subPromises = subDistricts.map((sub) => { |
| | | renderedDistrictAdcodes.add(sub.adcode); |
| | | |
| | | // 检查该区是否有数据 |
| | | // 模糊匹配:districtSet中的名字可能不带"区"字 |
| | | let hasData = false; |
| | | if (sub.name) { |
| | | for (let d of districtSet) { |
| | | if (sub.name.includes(d) || d.includes(sub.name)) { |
| | | hasData = true; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (hasData) { |
| | | // 有数据:默认高亮,无Hover特效 |
| | | return renderAreaNode( |
| | | sub.adcode, |
| | | { strokeColor: "#00ffd2", fillColor: "#00ffd2", zIndex: 10 }, |
| | | "district", |
| | | true, |
| | | false |
| | | ); |
| | | } else { |
| | | // 无数据:默认透明,有Hover特效 |
| | | return renderAreaNode( |
| | | sub.adcode, |
| | | { |
| | | strokeColor: "#00ffd2", |
| | | fillColor: "", |
| | | fillOpacity: 0, |
| | | zIndex: 10, |
| | | }, |
| | | "district", |
| | | false, |
| | | true |
| | | ); |
| | | } |
| | | }); |
| | | return Promise.all(subPromises); |
| | | } |
| | | }); |
| | | |
| | | await Promise.all(cityDistrictPromises); |
| | | |
| | | // 处理可能遗漏的区 (districtSet 中有,但没在上面的循环中渲染过) |
| | | const orphanDistrictPromises = Array.from(districtSet).map( |
| | | async (districtName) => { |
| | | // 先检查是否已经渲染过 (需要先resolve adcode才能精确匹配,但这里先用名字简单过滤一下,减少请求) |
| | | // 由于上面是根据adcode记录的,这里还是得resolve一下比较稳妥 |
| | | const result = await resolveAdcode(districtName, "district"); |
| | | if (result && !renderedDistrictAdcodes.has(result.adcode)) { |
| | | // 既然在districtSet里,肯定是有数据的 |
| | | return renderAreaNode( |
| | | result.adcode, |
| | | { strokeColor: "#00ffd2", fillColor: "#00ffd2", zIndex: 10 }, |
| | | "district", |
| | | true, |
| | | false |
| | | ); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | await Promise.all(orphanDistrictPromises); |
| | | |
| | | // 6. 自动调整视野 |
| | | const targetOverlays = []; |
| | | if (this.markers && this.markers.length > 0) { |
| | | targetOverlays.push(...this.markers); |
| | | } |
| | | |
| | | if (this.boundaryPolygons.length > 0) { |
| | | // 只保留 isActive 为 true 的区域用于缩放 (即有数据的区) |
| | | const activePolygons = this.boundaryPolygons.filter((p) => { |
| | | const data = p.getExtData(); |
| | | return data && data.isActive; |
| | | }); |
| | | targetOverlays.push(...activePolygons); |
| | | } |
| | | |
| | | if (targetOverlays.length > 0) { |
| | | this.mapView.setFitView(targetOverlays, false, [60, 60, 60, 60]); |
| | | } |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |