<template>
|
<div>
|
<HeaderBox />
|
<div
|
id="container"
|
style="
|
position: fixed;
|
top: 0;
|
left: 0;
|
z-index: 0;
|
width: 100vw;
|
height: 100vh;
|
background-color: #001635;
|
"
|
></div>
|
<!-- 类型切换tab -->
|
<div
|
class="flex a-center absolute postion-center top--109 br--16 c-fff fs--14 bgcolor4 SSHR"
|
>
|
<div @click="changeTab(1)" class="lh--32 px--20 cursor-pointer z-10">
|
在建工程
|
</div>
|
<div @click="changeTab(2)" class="lh--32 px--20 cursor-pointer z-10">
|
固定资产
|
</div>
|
<div class="activeBox" :class="activeTab == 2 && 'activeBox2'">
|
占位盒子
|
</div>
|
</div>
|
<!-- 左下角隐患 -->
|
<div
|
v-if="
|
(activeTab == 1 && permissionClient.includes('4')) ||
|
(activeTab == 2 && permissionClient.includes('6'))
|
"
|
class="flex a-center absolute left--14 bottom--14"
|
>
|
<div class="dangerBox">
|
<img
|
src="../../assets/img/home/Group@2x (4).png"
|
class="w--110 h--110 shrink0"
|
/>
|
<div @click="toHiddenDanger" class="hoverBox bgimg4 pt--10 c-fff cursor-pointer">
|
<div class="flex a-center ml--14">
|
<div class="ripple">
|
<div class="ripple-wave wave-1"></div>
|
<div class="ripple-wave wave-2"></div>
|
<div class="ripple-wave wave-3"></div>
|
</div>
|
<div class="fs--15 lh--15 ml--25">重大隐患数</div>
|
<div class="color5 ml--13 fs--30 lh--30 zh_tr">
|
{{ screenData.majorHazardTotal }}
|
</div>
|
</div>
|
<div class="flex a-center j-center gap--52 mt--11">
|
<div class="flex a-center">
|
<div class="fs--14 lh--14 ml--10">未整改</div>
|
<div class="color6 ml--13 fs--20 lh--24 zh_tr">
|
{{ screenData.unrectifiedHazardCount }}
|
</div>
|
</div>
|
<div class="flex a-center">
|
<div class="fs--14 lh--14 ml--10">已整改</div>
|
<div class="color7 ml--13 fs--20 lh--24 zh_tr">
|
{{ screenData.rectifiedHazardCount }}
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<!-- 地图legend -->
|
<div
|
class="fixed bottom--30 h--120 w--250 py--29 px--37 bgimg5"
|
style="right: 505px"
|
>
|
<div
|
v-if="activeTab == 1"
|
class="flex a-center j-between fs--12 SHSR c-fff"
|
>
|
<div
|
class="flex a-center gap--14 cursor-pointer"
|
@click="toggleCategory('municipal')"
|
>
|
<img
|
v-if="categoryFilter.municipal"
|
src="../../assets/img/home/Combined Shape@2x.png"
|
class="w--16 h--16 shrink0"
|
/>
|
<img
|
v-else
|
src="../../assets/img/home/11Combined Shape@2x.png"
|
class="w--16 h--16 shrink0"
|
/>
|
<div :class="!categoryFilter.municipal && 'gray-text'">市政</div>
|
</div>
|
<div
|
class="flex a-center gap--14 cursor-pointer"
|
@click="toggleCategory('house')"
|
>
|
<img
|
v-if="categoryFilter.house"
|
src="../../assets/img/home/Shape@2x (4).png"
|
class="w--18 h--16 shrink0"
|
/>
|
<img
|
v-else
|
src="../../assets/img/home/11Shape@2x.png"
|
class="w--18 h--16 shrink0"
|
/>
|
<div :class="!categoryFilter.house && 'gray-text'">房建</div>
|
</div>
|
</div>
|
<div v-else class="flex a-center j-center fs--12 SHSR c-fff">
|
<div
|
class="flex a-center gap--14 cursor-pointer"
|
@click="toggleCategory('assets')"
|
>
|
<img
|
v-if="categoryFilter.assets"
|
src="../../assets/img/home/assets.png"
|
class="w--18 h--16 shrink0"
|
/>
|
<img
|
v-else
|
src="../../assets/img/home/znull.png"
|
class="w--18 h--16 shrink0"
|
/>
|
<div :class="!categoryFilter.assets && 'gray-text'">资产</div>
|
</div>
|
</div>
|
<div class="flex a-center j-between gap--23 px--13 fs--12 SHSR mt--26">
|
<div
|
class="flex a-center flex-column cursor-pointer"
|
@click="toggleLevel('A')"
|
>
|
<div
|
class="w--8 h--8 mb--5 br50"
|
:class="levelFilter.A ? 'bgcolor8' : 'gray-bg'"
|
></div>
|
<div :class="levelFilter.A ? 'color8' : 'gray-text'">A类</div>
|
</div>
|
<div
|
class="flex a-center flex-column cursor-pointer"
|
@click="toggleLevel('C')"
|
>
|
<div
|
class="w--8 h--8 mb--5 br50"
|
:class="levelFilter.C ? 'bgcolor10' : 'gray-bg'"
|
></div>
|
<div :class="levelFilter.C ? 'color10' : 'gray-text'">B类</div>
|
</div>
|
<div
|
class="flex a-center flex-column cursor-pointer"
|
@click="toggleLevel('B')"
|
>
|
<div
|
class="w--8 h--8 mb--5 br50"
|
:class="levelFilter.B ? 'bgcolor9' : 'gray-bg'"
|
></div>
|
<div :class="levelFilter.B ? 'color9' : 'gray-text'">C类</div>
|
</div>
|
</div>
|
</div>
|
<div class="leftBox">
|
<!-- 日期搜索 -->
|
<div
|
@click="handleClick"
|
class="flex a-center j-between w100 h--40 relative bgcolor1 picker"
|
>
|
<el-date-picker
|
v-model="dateRange"
|
ref="datePicker"
|
type="daterange"
|
range-separator=""
|
start-placeholder=""
|
end-placeholder=""
|
>
|
</el-date-picker>
|
<div class="absolute w100 h100 flex a-center px--20">
|
<div
|
class="flex a-center c-fff fs--14 SHS"
|
style="width: calc(100% - 20px)"
|
>
|
{{ dateTxt }}
|
</div>
|
<div class="flex a-center gap--5">
|
<i
|
@click.stop="dateRange = []"
|
class="el-icon-circle-close c-fff cursor-pointer"
|
v-show="dateTxt"
|
/>
|
<img
|
src="../../assets/img/home/Shape@2x (6).png"
|
class="w--13 h--13 shrink0"
|
/>
|
</div>
|
</div>
|
</div>
|
<!-- 项目搜索 -->
|
<div
|
class="flex a-center j-between w100 pl--20 mt--13 h--40 relative bgcolor1 searchBox"
|
>
|
<el-select filterable v-model="projectId" placeholder="请输入项目名称">
|
<el-option
|
v-for="item in projectList"
|
:key="item.id"
|
:label="item.name"
|
:value="item.id"
|
>
|
</el-option>
|
</el-select>
|
<div
|
class="flex a-center gap--5 absolute"
|
style="top: -2px; right: -2px"
|
>
|
<i
|
@click.stop="projectId = null"
|
class="el-icon-circle-close c-fff cursor-pointer"
|
v-show="projectId"
|
/>
|
<div class="btn" @click="handleSearch">搜索</div>
|
</div>
|
</div>
|
<!-- 项目数据总览 -->
|
<div
|
v-if="
|
(activeTab == 1 && permissionClient.includes('2')) ||
|
(activeTab == 2 && permissionClient.includes('5'))
|
"
|
class="mt--33 relative"
|
>
|
<TitleBox>
|
<template #title> 项目数据总览 </template>
|
<template #content>
|
<div class="flex j-center px--20 mt--17 pb--16">
|
<div
|
@click="toPorjectList"
|
class="flex flex-column a-center mt--17 cursor-pointer"
|
>
|
<div class="bgImg1 flex a-center flex-column j-end">
|
<div class="mb--23 c-fff SHSBold lh--18 fs--18 txts1">
|
项目总数
|
</div>
|
</div>
|
<div
|
class="color1 fs--34 lh--39 txts2 ff54-"
|
style="margin-top: -20px"
|
>
|
{{ screenData.projectTotal }}
|
</div>
|
</div>
|
<!-- 在建工程tab -->
|
<div class="ml--18 flex1" v-if="activeTab == 1">
|
<div
|
@click="toPorjectList"
|
class="flex a-center pl--11 pr--13 py--6 bgimg2 cursor-pointer thisBox relative"
|
>
|
<div class="c-fff fs--14 SHSR mr--29 lh--14 shrink0">
|
本月检查情况
|
</div>
|
<img
|
src="../../assets/img/home/Shape@2x.png"
|
class="w--14 h--14 shrink0"
|
/>
|
<div
|
@click="toPorjectList"
|
class="hoverBox pt--20 px--20 pb--26"
|
>
|
<div class="c-fff SHSBold fs--15 lh--15">本月检查情况</div>
|
<div class="flex a-center j-between mt--20">
|
<div class="px--13 py--13 bsh1" style="min-width: 150px">
|
<div class="c-fff fs---14 lh--14 SHSH">已检查数:</div>
|
<div class="fs--45 lh--45 ff54- color8">
|
{{ screenData.inspectedCount }}
|
</div>
|
<div class="mt--20 c-fff SHSR fs--13 lh--25">
|
<div>
|
房建:{{ screenData.inspectedHousingCount }}
|
</div>
|
<div>
|
市政:{{ screenData.inspectedMunicipalCount }}
|
</div>
|
</div>
|
</div>
|
<div class="px--13 py--13 bsh2" style="min-width: 150px">
|
<div class="c-fff fs---14 lh--14 SHSH">未检查数:</div>
|
<div class="fs--45 lh--45 ff54- color6">
|
{{ screenData.uninspectedCount }}
|
</div>
|
<div class="mt--20 c-fff SHSR fs--13 lh--25">
|
<div>
|
房建:{{ screenData.uninspectedHousingCount }}
|
</div>
|
<div>
|
市政:{{ screenData.uninspectedMunicipalCount }}
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="mt--13 flex bgcolor2 pl--9 pb--5 pt--4 pr--10">
|
<div class="flex flex1 mt--7 shrink0">
|
<img
|
src="../../assets/img/home/Shape@2x (5).png"
|
class="w--31 h--30 shrink0"
|
/>
|
<div class="ml--7 SHSR shrink0">
|
<div class="color2 fs--14 lh--14">房建</div>
|
<div
|
v-if="!dateRange.length"
|
class="mt--9 fs--8 lh--8 color3"
|
>
|
本月检查覆盖率
|
</div>
|
</div>
|
</div>
|
<div class="ml--11 txt-center">
|
<div class="c-fff fs--22 lh--22 SHSBold">
|
{{ screenData.housingProjectCount }}
|
</div>
|
<div
|
v-if="!dateRange.length"
|
class="mt--9 fs--10 lh--12 color4 SHSM"
|
>
|
{{ screenData.housingInspectionCoverage }}%
|
</div>
|
</div>
|
</div>
|
<div class="mt--10 flex bgcolor2 pl--9 pb--5 pt--4 pr--10">
|
<div class="flex flex1 mt--7 shrink0">
|
<img
|
src="../../assets/img/home/Combined Shape@2x (4).png"
|
class="w--30 h--30 shrink0"
|
/>
|
<div class="ml--7 SHSR shrink0">
|
<div class="color2 fs--14 lh--14">市政</div>
|
<div
|
v-if="!dateRange.length"
|
class="mt--9 fs--8 lh--8 color3"
|
>
|
本月检查覆盖率
|
</div>
|
</div>
|
</div>
|
<div class="ml--11 txt-center">
|
<div class="c-fff fs--22 lh--22 SHSBold">
|
{{ screenData.municipalProjectCount }}
|
</div>
|
<div
|
v-if="!dateRange.length"
|
class="mt--9 fs--10 lh--12 color4 SHSM"
|
>
|
{{ screenData.municipalInspectionCoverage }}%
|
</div>
|
</div>
|
</div>
|
</div>
|
<!-- 固定资产tab -->
|
<div class="ml--18 flex1" v-else>
|
<div
|
class="mt--13 flex a-center j-between bgcolor2 pl--9 pb--5 pt--4 pr--10"
|
>
|
<div class="flex flex1 mt--7 shrink0">
|
<img
|
src="../../assets/img/home/Shape@2x (5).png"
|
class="w--31 h--30 shrink0"
|
/>
|
<div class="ml--7 SHSR shrink0">
|
<div class="color2 fs--14 lh--14">已检查</div>
|
</div>
|
</div>
|
<div class="c-fff fs--22 lh--22 SHSBold">
|
{{ screenData.inspectedCount }}
|
</div>
|
</div>
|
<div
|
class="mt--10 flex a-center j-between bgcolor2 pl--9 pb--5 pt--4 pr--10"
|
>
|
<div class="flex flex1 mt--7 shrink0">
|
<img
|
src="../../assets/img/home/Shape@2x (5).png"
|
class="w--30 h--30 shrink0"
|
/>
|
<div class="ml--7 SHSR shrink0">
|
<div class="color2 fs--14 lh--14">未检查</div>
|
</div>
|
</div>
|
<div class="c-fff fs--22 lh--22 SHSBold">
|
{{ screenData.uninspectedCount }}
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
</TitleBox>
|
</div>
|
<!-- 设备数据总览 -->
|
<div
|
class="mt--25"
|
v-if="activeTab == 1 && permissionClient.includes('3')"
|
>
|
<TitleBox>
|
<template #title> 设备数据总览 </template>
|
<template #content>
|
<div class="flex j-end mt--8 mr--18">
|
<div
|
@click="toEquipmentList"
|
class="flex a-center pl--11 pr--13 py--6 bgimg2 cursor-pointer thisBox relative"
|
>
|
<div class="c-fff fs--14 SHSR mr--29 lh--14 shrink0">
|
本月检查情况
|
</div>
|
<img
|
src="../../assets/img/home/Shape@2x.png"
|
class="w--14 h--14 shrink0"
|
/>
|
<div
|
@click="toEquipmentList"
|
class="hoverBox pt--20 px--20 pb--26"
|
>
|
<div class="c-fff SHSBold fs--15 lh--15">本月检查情况</div>
|
<div class="flex a-center j-between mt--20">
|
<div class="px--13 py--13 bsh1" style="min-width: 150px">
|
<div class="c-fff fs---14 lh--14 SHSH">已检查数:</div>
|
<div class="fs--45 lh--45 ff54- color8">
|
{{ screenData.inspectedMachineryTotal }}
|
</div>
|
<div class="mt--20 oa">
|
<div
|
v-for="(
|
item, index
|
) in screenData.machineryStatistics"
|
:key="index"
|
class="c-fff SHSR fs--13 lh--25"
|
>
|
<div>{{ item.name }}:{{ item.inspectedCount }}</div>
|
</div>
|
</div>
|
</div>
|
<div class="px--13 py--13 bsh2" style="min-width: 150px">
|
<div class="c-fff fs---14 lh--14 SHSH">未检查数:</div>
|
<div class="fs--45 lh--45 ff54- color6">
|
{{ screenData.uninspectedMachineryTotal }}
|
</div>
|
<div class="mt--20 oa">
|
<div
|
v-for="(
|
item, index
|
) in screenData.machineryStatistics"
|
:key="index"
|
class="c-fff SHSR fs--13 lh--25"
|
>
|
<div>
|
{{ item.name }}:{{ item.uninspectedCount }}
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div
|
@click="toEquipmentList"
|
class="flex a-center j-center gap--40 mt--14 mx--20 br--10 py--14 bgcolor3 cursor-pointer"
|
>
|
<img
|
src="../../assets/img/home/15@2x.png"
|
class="w--72 h--77 shrink0"
|
/>
|
<div>
|
<div class="fs--34 lh--39 color1 txts2 ff54-">
|
{{ screenData.machineryTotal }}
|
</div>
|
<div class="c-fff fs--16 lh--16 SHSBold mt--10">
|
大型机械设备数
|
</div>
|
</div>
|
</div>
|
<div class="mt--14 mx--20 pb--10 rowItem h--250 oa">
|
<div
|
@click="toEquipmentList"
|
v-for="(item, index) in screenData.machineryStatistics"
|
:key="index"
|
class="cursor-pointer"
|
>
|
<div class="bgimg3">
|
<div class="c-fff fs--22 lh--33 SHSBold txt-center">
|
{{ item.total }}
|
</div>
|
</div>
|
<div class="fs--12 lh--12 color5 nameTxt">{{ item.name }}</div>
|
</div>
|
</div>
|
</template>
|
</TitleBox>
|
</div>
|
</div>
|
<div class="rightBox">
|
<div class="mt--33" v-if="permissionClient.includes('7')">
|
<TitleBox>
|
<template #title> 事故简讯 </template>
|
<template #right>
|
<div class="flex a-center gap--10">
|
<div class="flex a-center searchTabs">
|
<div
|
@click="handleTabClick(item)"
|
v-for="item in searchTabs"
|
:key="item"
|
:class="{ activeTabs: item === activeTabs }"
|
>
|
{{ item }}
|
</div>
|
</div>
|
<div
|
@click="toAccidentCaseList"
|
class="flex a-center py--6 pl--10 pr--8 br--4 bgcolor5 cursor-pointer"
|
>
|
<div class="fs--12 lh--12 c-fff mr--4 SHSR">详情</div>
|
<img
|
src="../../assets/img/home/13@2x.png"
|
class="w--14 h--14 shrink0"
|
/>
|
</div>
|
</div>
|
</template>
|
<template #content>
|
<div
|
ref="scrollContainer"
|
class="scroll-container"
|
style="height: 408px"
|
@mouseenter="handleMouseEnter"
|
@mouseleave="handleMouseLeave"
|
@wheel="handleWheel"
|
>
|
<div>
|
<div
|
@click="toAccidentCaseDetail(item)"
|
v-for="(item, index) in scrollData"
|
:key="`${item.id}-${index}`"
|
:class="(index + 1) % 2 === 0 ? 'bgcolor7' : ''"
|
class="relative py--20 pl--20 pr--32 c-fff"
|
>
|
<div
|
class="absolute lh--20 fs--12 SHSBold bgcolor6 px--6 top0 right0"
|
>
|
{{ item.classifyName }}
|
</div>
|
<div class="fs--16 lh--16 SHSBold">
|
{{ item.title }}
|
</div>
|
<div class="flex a-center mt--14">
|
<img
|
src="../../assets/img/home/Shape@2x (2).png"
|
class="w--8 h--10 shrink0 mr--7"
|
/>
|
<div class="SHSN fs--14 lh--14">
|
{{ item.province + item.city + item.area }}
|
</div>
|
</div>
|
<div class="flex a-center j-between mt--8 pr--76">
|
<div class="flex a-center">
|
<img
|
src="../../assets/img/home/Shape@2x (3).png"
|
class="w--10 h--9 shrink0 mr--7"
|
/>
|
<div class="SHSN fs--14 lh--14">{{ item.time }}</div>
|
</div>
|
<div class="flex a-center">
|
<img
|
src="../../assets/img/home/Shape@2x (1).png"
|
class="w--9 h--10 shrink0 mr--7"
|
/>
|
<div class="SHSN fs--14 lh--14">
|
伤亡:造成
|
<span class="SHSBold fs--14">{{
|
item.casualties
|
}}</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
</TitleBox>
|
</div>
|
<div class="mt--33" v-if="permissionClient.includes('8')">
|
<TitleBox>
|
<template #title> 工作展示 </template>
|
<template #right>
|
<div
|
@click="toWorkList"
|
class="flex a-center py--6 pl--10 pr--8 br--4 bgcolor5 cursor-pointer"
|
>
|
<div class="fs--12 lh--12 c-fff mr--4 SHSR">详情</div>
|
<img
|
src="../../assets/img/home/13@2x.png"
|
class="w--14 h--14 shrink0"
|
/>
|
</div>
|
</template>
|
<template #content>
|
<div class="px--19 pt--5 pb--9">
|
<el-carousel
|
height="366px"
|
indicator-position="none"
|
:interval="5000"
|
arrow="never"
|
ref="carousel"
|
@change="handleChangeCarousel"
|
>
|
<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"
|
/>
|
<div
|
class="fs--18 lh--24 SHSBold py--10 pl--20 pr--14 c-fff"
|
>
|
{{ item.title }}
|
</div>
|
</div>
|
</el-carousel-item>
|
</el-carousel>
|
<div class="flex a-center justify-center mt--9">
|
<div class="flex a-center gap--3">
|
<img
|
v-for="(item, index) in workListData"
|
:key="item.id"
|
:src="
|
activeIndex === index
|
? require('../../assets/img/home/Rectangle@2x.png')
|
: require('../../assets/img/home/Rectangle@2x (1).png')
|
"
|
class="w--18 h--4 shrink0 cursor-pointer"
|
@click="changeActiveIndex(index)"
|
/>
|
</div>
|
</div>
|
</div>
|
</template>
|
</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>
|
|
<script>
|
import HeaderBox from '../header/index.vue'
|
import moment from 'moment'
|
import s1 from '../../assets/img/home/s1.png'
|
import s2 from '../../assets/img/home/s2.png'
|
import s3 from '../../assets/img/home/s3.png'
|
import f1 from '../../assets/img/home/f1.png'
|
import f2 from '../../assets/img/home/f2.png'
|
import f3 from '../../assets/img/home/f3.png'
|
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,
|
getProjectSelect,
|
getWorkPresentationList,
|
getprojectTooltip
|
} from './service'
|
|
export default {
|
components: {
|
HeaderBox
|
},
|
data () {
|
return {
|
mapView: null,
|
mapPromise: null,
|
dateRange: [moment().startOf('month'), moment().endOf('month')], // 默认本月
|
dateTxt: '',
|
activeTab: 1,
|
scrollData: [], // 存储滚动数据
|
scrollTimer: null, // 滚动定时器
|
scrollContainer: null, // 滚动容器引用
|
activeIndex: 0, // 当前活动索引
|
projectId: null,
|
categoryFilter: {
|
municipal: true,
|
house: true,
|
assets: true
|
},
|
levelFilter: {
|
A: true,
|
B: true,
|
C: true
|
},
|
markers: [],
|
// 2项目总数(在建工程) 3大型机械设备数(在建工程) 4重大隐患数(在建工程) 5项目总数(固定资产) 6重大隐患数(固定资产) 7事故简讯 8工作展示
|
permissionClient: JSON.parse(localStorage.getItem('permissionClient')),
|
// 9各分项隐患趋势 10隐患数量 11设备数量 12查看报告入口 13人员在岗率 14重大风险源 15 项目风险趋势
|
permissionProject: JSON.parse(localStorage.getItem('permissionProject')),
|
screenData: {},
|
searchTabs: ['本周', '本月', '本季度', '本年'],
|
activeTabs: '本周',
|
workListData: [],
|
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: {
|
dateRange: {
|
handler (newVal, oldVal) {
|
if (newVal.length > 0) {
|
this.dateTxt =
|
moment(newVal[0]).format('YYYY年MM月DD日') +
|
' ~ ' +
|
moment(newVal[1]).format('YYYY年MM月DD日')
|
} else {
|
this.dateTxt = ''
|
}
|
this.$nextTick(() => {
|
this.getScreenData()
|
})
|
},
|
immediate: true
|
}
|
},
|
created () {
|
if (localStorage.getItem('activeTab')) {
|
this.activeTab = Number(localStorage.getItem('activeTab'))
|
}
|
this.mapPromise = AMapLoader.load({
|
key: '232929db1cb31f26d42b3645a7456154',
|
version: '2.0',
|
plugins: ['AMap.DistrictSearch', 'AMap.Geocoder'],
|
AMapUI: {
|
version: '1.1',
|
plugins: ['geo/DistrictExplorer']
|
}
|
})
|
this.getStoryData()
|
getWorkPresentationList({
|
clientId: localStorage.getItem('clientId'),
|
state: 1,
|
pageNum: 1,
|
pageSize: 9999
|
}).then((res) => {
|
this.workListData = res.records
|
})
|
},
|
mounted () {
|
this.$nextTick(() => {
|
this.scrollContainer = this.$refs.scrollContainer
|
})
|
},
|
beforeDestroy () {
|
localStorage.setItem('activeTab', this.activeTab)
|
this.stopAutoScroll()
|
if (this._mapResizeHandler) {
|
window.removeEventListener('resize', this._mapResizeHandler)
|
this._mapResizeHandler = null
|
}
|
},
|
methods: {
|
handleTabClick (tab) {
|
this.activeTabs = tab
|
this.getStoryData()
|
},
|
changeTab (type) {
|
if (this.activeTab == type) return
|
this.activeTab = type
|
this.getScreenData()
|
},
|
getStoryData () {
|
let startTime = moment()
|
let endTime = moment()
|
if (this.activeTabs == '本周') {
|
startTime = moment().startOf('isoWeek')
|
endTime = moment().endOf('isoWeek')
|
} else if (this.activeTabs == '本月') {
|
startTime = moment().startOf('month')
|
endTime = moment().endOf('month')
|
} else if (this.activeTabs == '本季度') {
|
startTime = moment().startOf('quarter')
|
endTime = moment().endOf('quarter')
|
} else if (this.activeTabs == '本年') {
|
startTime = moment().startOf('year')
|
endTime = moment().endOf('year')
|
}
|
let time =
|
startTime.format('YYYY-MM-DD') + ' - ' + endTime.format('YYYY-MM-DD')
|
getStoryList({
|
time: time
|
}).then((res) => {
|
this.scrollData = res
|
if (this.scrollData.length > 4) {
|
this.initScrollData()
|
}
|
})
|
},
|
|
getScreenData () {
|
let params = {
|
projectCategory: ['ongoing', 'fixed'][this.activeTab - 1],
|
startDate: '',
|
endDate: '',
|
clientId: localStorage.getItem('clientId')
|
}
|
if (this.dateRange.length) {
|
params.startDate = moment(this.dateRange[0]).format('YYYY-MM-DD')
|
params.endDate = moment(this.dateRange[1]).format('YYYY-MM-DD')
|
}
|
getProjectSelect(params).then((res) => {
|
this.projectList = res
|
})
|
getStatistics(params).then((res) => {
|
this.screenData = res.ongoingProject || res.fixedAsset
|
this.setMapload()
|
})
|
},
|
toAccidentCaseDetail (item) {
|
this.$router.push({
|
path: '/accidentCase/detail',
|
query: {
|
id: item.id
|
}
|
})
|
},
|
toAccidentCaseList () {
|
this.$router.push('/accidentCase')
|
},
|
toWorkDetail (item) {
|
this.$router.push({
|
path: '/workList/detail',
|
query: {
|
id: item.id
|
}
|
})
|
},
|
toWorkList () {
|
this.$router.push('/workList')
|
},
|
toPorjectList () {
|
const query = {
|
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.endTime = moment(this.dateRange[1]).format('YYYY-MM-DD HH:mm:ss')
|
}
|
|
this.$router.push({
|
path: '/projectList',
|
query: query
|
})
|
},
|
toEquipmentList () {
|
const query = {}
|
|
// 如果有选择日期范围,则传递开始和结束时间
|
if (this.dateRange && this.dateRange.length === 2) {
|
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
|
})
|
},
|
toHiddenDanger () {
|
const query = {
|
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.endTime = moment(this.dateRange[1]).format('YYYY-MM-DD HH:mm:ss')
|
}
|
|
this.$router.push({
|
path: '/hiddenDanger',
|
query: query
|
})
|
},
|
// 切换活动索引
|
changeActiveIndex (index) {
|
this.activeIndex = index
|
this.$refs.carousel.setActiveItem(index)
|
},
|
// 切换活动索引
|
handleChangeCarousel (index) {
|
this.activeIndex = index
|
},
|
handleClick () {
|
this.$refs.datePicker.focus()
|
},
|
handleSearch () {
|
if (!this.projectId) {
|
return
|
}
|
|
const target = this.screenData.projectMapPoints.find(
|
(item) => item.projectId === this.projectId
|
)
|
|
if (!target || Object.keys(target).length == 0) {
|
this.$message.warning('无此项目地图点位')
|
return
|
}
|
|
if (target.longitude && target.latitude) {
|
// 定位地图
|
this.mapView.setZoomAndCenter(15, [
|
Number(target.longitude),
|
Number(target.latitude)
|
])
|
|
// 显示悬浮窗
|
setTimeout(() => {
|
this.showTooltip(null, {
|
projectId: target.projectId,
|
position: [Number(target.longitude), Number(target.latitude)]
|
})
|
}, 1000)
|
} else {
|
this.$message.warning('该项目暂无位置信息')
|
}
|
},
|
// 初始化滚动数据
|
initScrollData () {
|
this.stopAutoScroll()
|
if (!this.scrollData || this.scrollData.length === 0) return
|
|
// 为了实现无缝滚动,复制2份数据
|
this.scrollData = [...this.scrollData, ...this.scrollData]
|
|
// 开始滚动
|
this.$nextTick(() => {
|
this.startAutoScroll()
|
})
|
},
|
|
// 开始自动滚动
|
startAutoScroll () {
|
this.stopAutoScroll()
|
|
if (!this.scrollContainer || !this.scrollData.length) return
|
|
const originalDataLength = this.scrollData.length / 2
|
|
// 动态计算高度
|
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
|
|
const scroll = () => {
|
let currentScrollTop = this.scrollContainer.scrollTop
|
|
// 当滚动到第二份的末尾时
|
if (currentScrollTop >= firstHalfHeight) {
|
// 计算当前滚动位置相对于第二份数据开始的偏移量
|
const offset = currentScrollTop - firstHalfHeight
|
// 直接跳到第一份数据相同偏移量的位置
|
this.scrollContainer.scrollTop = offset
|
// 设置标志表示刚刚进行过跳转
|
shouldJump = true
|
} else {
|
// 如果在跳转后,并且位置重置到了0附近,重置标志
|
if (shouldJump && currentScrollTop < 2) {
|
shouldJump = false
|
}
|
}
|
|
// 只有在不处于跳转状态时才继续滚动
|
if (!shouldJump) {
|
this.scrollContainer.scrollTop += 1
|
}
|
|
this.scrollTimer = requestAnimationFrame(scroll)
|
}
|
|
// 开始滚动
|
this.scrollTimer = requestAnimationFrame(scroll)
|
},
|
|
// 停止自动滚动
|
stopAutoScroll () {
|
if (this.scrollTimer) {
|
cancelAnimationFrame(this.scrollTimer)
|
this.scrollTimer = null
|
}
|
},
|
|
// 鼠标进入容器时暂停滚动
|
handleMouseEnter () {
|
this.stopAutoScroll()
|
},
|
|
// 鼠标离开容器时恢复滚动
|
handleMouseLeave () {
|
this.startAutoScroll()
|
},
|
|
// 处理滚轮事件
|
handleWheel (event) {
|
event.preventDefault()
|
this.scrollContainer.scrollTop += event.deltaY
|
},
|
|
setMapload () {
|
if (!this.mapPromise) {
|
this.mapPromise = AMapLoader.load({
|
key: '232929db1cb31f26d42b3645a7456154',
|
version: '2.0',
|
plugins: ['AMap.DistrictSearch', 'AMap.Geocoder'],
|
AMapUI: {
|
version: '1.1',
|
plugins: ['geo/DistrictExplorer']
|
}
|
})
|
}
|
this.mapPromise
|
.then((AMap) => {
|
this.mapView = new AMap.Map('container', {
|
zoom: 13,
|
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) // 加载错误提示
|
})
|
},
|
initCategoryMarkers (AMap) {
|
if (!this.mapView) {
|
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: category,
|
level: item.category
|
}
|
})
|
const markers = []
|
points.forEach((item) => {
|
const marker = this.createCategoryMarker(AMap, item)
|
if (marker) {
|
this.mapView.add(marker)
|
markers.push(marker)
|
}
|
})
|
this.markers = markers
|
this.updateMarkerVisibility()
|
},
|
createCategoryMarker (AMap, item) {
|
if (!item || !item.position) {
|
return null
|
}
|
let image = null
|
let level = item.level
|
if (item.category === 'municipal') {
|
if (item.level === 'A') {
|
image = s1
|
} else if (item.level === 'B') {
|
image = s3
|
} else if (item.level === 'C') {
|
image = s2
|
} else {
|
image = s4
|
}
|
} else if (item.category === 'house') {
|
if (item.level === 'A') {
|
image = f1
|
} else if (item.level === 'B') {
|
image = f3
|
} else if (item.level === 'C') {
|
image = f2
|
} else {
|
image = f4
|
}
|
} else if (item.category === 'assets') {
|
if (item.level === 'A') {
|
image = z1
|
} else if (item.level === 'B') {
|
image = z3
|
} else if (item.level === 'C') {
|
image = z2
|
} else {
|
image = z4
|
}
|
}
|
if (!image) {
|
return null
|
}
|
const icon = new AMap.Icon({
|
image,
|
size: new AMap.Size(30, 30),
|
imageSize: new AMap.Size(30, 30)
|
})
|
const marker = new AMap.Marker({
|
position: item.position,
|
icon,
|
offset: new AMap.Pixel(-12, -12),
|
extData: {
|
category: item.category,
|
level: level
|
}
|
})
|
marker.on('mouseover', (e) => {
|
this.showTooltip(e, item)
|
})
|
marker.on('mouseout', () => {
|
this.hideTooltip()
|
})
|
marker.on('click', () => {
|
this.$router.push({
|
path: '/projectHome',
|
query: {
|
projectId: item.projectId,
|
projectName: item.projectName
|
}
|
})
|
})
|
return marker
|
},
|
updateMarkerVisibility () {
|
if (!this.markers || !this.markers.length) {
|
return
|
}
|
this.markers.forEach((marker) => {
|
if (!marker) {
|
return
|
}
|
const data = marker.getExtData ? marker.getExtData() : null
|
if (!data) {
|
return
|
}
|
const categoryVisible = this.categoryFilter[data.category]
|
const levelVisible = !data.level || this.levelFilter[data.level]
|
if (categoryVisible && levelVisible) {
|
marker.show()
|
} else {
|
marker.hide()
|
}
|
})
|
},
|
toggleCategory (type) {
|
if (!this.categoryFilter.hasOwnProperty(type)) {
|
return
|
}
|
this.categoryFilter[type] = !this.categoryFilter[type]
|
this.updateMarkerVisibility()
|
},
|
toggleLevel (level) {
|
if (!this.levelFilter.hasOwnProperty(level)) {
|
return
|
}
|
this.levelFilter[level] = !this.levelFilter[level]
|
this.updateMarkerVisibility()
|
},
|
showTooltip (e, item) {
|
if (!this.mapView) return
|
|
const pixel = this.mapView.lngLatToContainer(item.position)
|
|
// 获取当前缩放比例
|
let scaleX = 1
|
let scaleY = 1
|
const app = document.querySelector('#app')
|
if (app) {
|
const rect = app.getBoundingClientRect()
|
// offsetWidth 是元素的布局宽度(不包含 transform)
|
// rect.width 是元素的渲染宽度(包含 transform)
|
if (app.offsetWidth > 0) {
|
scaleX = rect.width / app.offsetWidth
|
}
|
if (app.offsetHeight > 0) {
|
scaleY = rect.height / app.offsetHeight
|
}
|
}
|
|
this.tooltipPos = { left: pixel.x / scaleX, top: pixel.y / scaleY - 60 }
|
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)
|
}
|
|
// 对设备租赁单位进行去重处理
|
let uniqueProperties = []
|
if (res.properties && res.properties.length > 0) {
|
const seenNames = new Set()
|
uniqueProperties = res.properties.filter((item) => {
|
try {
|
const parsed = JSON.parse(item)
|
const companyName = parsed['工程起重机械租赁单位']
|
if (companyName && !seenNames.has(companyName)) {
|
seenNames.add(companyName)
|
return true
|
}
|
return false
|
} catch (e) {
|
return false
|
}
|
})
|
}
|
|
this.tooltipData = {
|
...res,
|
address,
|
constructionCompanyInfo,
|
supervisorCompanyInfo,
|
developmentCompanyInfo,
|
properties: uniqueProperties
|
}
|
this.tooltipLoading = false
|
})
|
.catch((err) => {
|
this.tooltipLoading = false
|
this.tooltipError = true
|
})
|
},
|
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:
|
level === 'district' && (isActive || isHoverable)
|
? 'pointer'
|
: 'default',
|
extData: { level, isActive }
|
})
|
|
// 添加 hover 事件
|
if (level === 'district' && (isActive || isHoverable)) {
|
const baseFillColor = style.fillColor || 'transparent'
|
const baseFillOpacity =
|
style.fillOpacity !== undefined ? style.fillOpacity : 0.2
|
const baseStrokeColor = style.strokeColor
|
const hoverStyle = isActive
|
? {
|
fillColor: '#00b3ff',
|
fillOpacity: 0.35,
|
strokeColor: '#00b3ff',
|
strokeWeight: 3
|
}
|
: {
|
fillColor: '#00ffd2',
|
fillOpacity: 0.2
|
}
|
|
polygon.on('mouseover', () => {
|
polygon.setOptions(hoverStyle)
|
})
|
polygon.on('mouseout', () => {
|
polygon.setOptions({
|
fillColor: baseFillColor,
|
fillOpacity: baseFillOpacity,
|
strokeColor: baseStrokeColor,
|
strokeWeight: 2
|
})
|
})
|
}
|
|
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>
|
|
<style lang="less" scoped>
|
.bsh1 {
|
box-shadow: inset 0px 0px 22px 0px rgba(11, 220, 140, 0.35);
|
}
|
|
.bsh2 {
|
box-shadow: inset 0px 0px 22px 0px rgba(254, 219, 101, 0.3);
|
}
|
|
.bgimg4 {
|
background-image: url("../../assets/img/home/Group 24 Copy@2x.png");
|
background-size: 100% 100%;
|
width: 300px;
|
height: 94px;
|
}
|
|
.bgimg5 {
|
background-image: url("../../assets/img/home/25@2x.png");
|
background-size: 100% 100%;
|
}
|
|
.gray-text {
|
color: rgba(255, 255, 255, 0.5) !important;
|
}
|
|
.gray-bg {
|
background: rgba(255, 255, 255, 0.5) !important;
|
}
|
|
.color5 {
|
color: #ff4d04;
|
}
|
|
.color6 {
|
color: #fedb65;
|
}
|
|
.color7 {
|
color: #47a1ff;
|
}
|
|
.color8 {
|
color: #00ffd2;
|
}
|
|
.color9 {
|
color: #ff4d4f;
|
}
|
|
.color10 {
|
color: #ffc53d;
|
}
|
|
.bgcolor4 {
|
background: #06175b;
|
border-radius: 16px;
|
border: 1px solid #204fb3;
|
}
|
|
.bgcolor5 {
|
background: rgba(14, 93, 251, 0.5);
|
}
|
|
.bgcolor6 {
|
background: linear-gradient(180deg, #ff4d4f 0%, #e5484d 100%);
|
border-radius: 2px 0px 0px 10px;
|
border: 1px solid #e5484d;
|
}
|
|
.bgcolor7 {
|
background: rgba(14, 95, 255, 0.2);
|
}
|
|
.bgcolor8 {
|
background: #00ffd2;
|
}
|
|
.bgcolor9 {
|
background: #ff4d4f;
|
}
|
|
.bgcolor10 {
|
background: #ffc53d;
|
}
|
|
.activeBox {
|
position: absolute;
|
top: 0;
|
left: 0;
|
padding: 0 20px;
|
height: 32px;
|
background: #0600e4;
|
color: transparent;
|
box-shadow: inset 0px 0px 20px 0px rgba(102, 255, 255, 0.52);
|
border-radius: 20px;
|
border: 1px solid rgba(50, 197, 255, 0.22);
|
transition: all 0.3s ease-in-out;
|
}
|
|
.activeBox2 {
|
left: 95px;
|
}
|
|
.leftBox {
|
width: 380px;
|
position: fixed;
|
top: 109px;
|
left: 23px;
|
|
.bgImg1 {
|
background-image: url("../../assets/img/home/Group@2x (3).png");
|
background-size: 100% 100%;
|
width: 171px;
|
height: 120px;
|
}
|
|
.bgimg2 {
|
background-image: url("../../assets/img/home/Group@2x (2).png");
|
background-size: 100% 100%;
|
}
|
|
.bgimg3 {
|
background-image: url("../../assets/img/home/@3x@2x.png");
|
background-size: 100% 100%;
|
width: 45px;
|
height: 49px;
|
}
|
|
.txts1 {
|
text-shadow: 0px 0px 7px #132c5a;
|
}
|
|
.txts2 {
|
text-shadow: 0px 0px 12px #1068f7;
|
}
|
|
.bgcolor1 {
|
background: rgba(2, 0, 77, 0.8);
|
box-shadow: inset 0px 0px 34px 0px rgba(17, 40, 255, 0.66);
|
border: 2px solid #0d53b7;
|
}
|
|
.bgcolor2 {
|
background: rgba(0, 113, 251, 0.1);
|
}
|
|
.bgcolor3 {
|
box-shadow: inset 0px 0px 22px 0px #2548b1;
|
border: 1px solid #3341b6;
|
}
|
|
.color1 {
|
color: #66ffff;
|
}
|
|
.color2 {
|
color: #cce0f8;
|
}
|
|
.color3 {
|
color: #7cc1ff;
|
}
|
|
.color4 {
|
color: #fedb65;
|
}
|
|
.color5 {
|
color: rgba(204, 224, 248, 0.7);
|
}
|
|
.hoverBox {
|
background-image: url("../../assets/img/home/444Group 24 Copy@2x.png");
|
background-size: 100% 100%;
|
width: 358px;
|
}
|
|
.picker {
|
::v-deep .el-input__icon {
|
display: none;
|
}
|
|
::v-deep .el-range-separator {
|
display: none;
|
}
|
|
::v-deep .el-input__inner {
|
background: transparent;
|
border: none;
|
color: transparent;
|
}
|
|
::v-deep .el-range-input {
|
background: transparent;
|
border: none;
|
color: transparent;
|
}
|
}
|
|
.searchBox {
|
::v-deep .el-input__inner {
|
background: transparent;
|
border: none;
|
color: #fff;
|
padding: 0;
|
}
|
|
::v-deep .el-input__suffix {
|
display: none;
|
}
|
|
.btn {
|
background: linear-gradient(
|
90deg,
|
#3467d0 0%,
|
#0d226a 50.35%,
|
#376fe0 100%
|
);
|
line-height: 38px;
|
font-size: 14px;
|
color: #ffffff;
|
font-family: "Source Han Sans CN Heavy";
|
padding: 0 26px;
|
border: 1px solid #307ad9;
|
box-sizing: border-box;
|
cursor: pointer;
|
}
|
}
|
|
.rowItem {
|
display: grid;
|
grid-template-columns: repeat(4, 1fr);
|
gap: 14px 33px;
|
|
.nameTxt {
|
width: 5em;
|
overflow: hidden;
|
max-height: 2em;
|
text-align: center;
|
}
|
}
|
}
|
|
.ripple {
|
position: fixed;
|
width: 14px;
|
height: 14px;
|
margin-right: 14px;
|
}
|
|
.ripple-wave {
|
position: absolute;
|
border-radius: 50%;
|
left: 50%;
|
top: 50%;
|
transform: translate(-50%, -50%);
|
}
|
|
.wave-1 {
|
width: 14px;
|
height: 14px;
|
background-color: rgba(255, 77, 4, 1);
|
}
|
|
.wave-2 {
|
width: 20px;
|
height: 20px;
|
background-color: rgba(255, 77, 4, 0.6);
|
animation: ripple-effect 2s ease-out infinite;
|
}
|
|
.wave-3 {
|
width: 24px;
|
height: 24px;
|
background-color: rgba(255, 77, 4, 0.4);
|
animation: ripple-effect 2s ease-out infinite 1s;
|
}
|
|
@keyframes ripple-effect {
|
0% {
|
transform: translate(-50%, -50%) scale(0.7);
|
opacity: 0;
|
}
|
20% {
|
opacity: 1;
|
opacity: 1;
|
}
|
100% {
|
-webkit-transform: translate(-50%, -50%) scale(1.4);
|
opacity: 0;
|
}
|
}
|
|
.rightBox {
|
width: 462px;
|
position: fixed;
|
top: 85px;
|
right: 20px;
|
}
|
|
/* 无缝滚动容器 */
|
.scroll-container {
|
overflow-y: auto;
|
overflow-x: hidden;
|
scrollbar-width: thin;
|
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
|
-ms-overflow-style: none;
|
scrollbar-width: none;
|
|
&::-webkit-scrollbar {
|
width: 6px;
|
}
|
|
&::-webkit-scrollbar-track {
|
background: transparent;
|
}
|
|
&::-webkit-scrollbar-thumb {
|
background-color: rgba(255, 255, 255, 0.3);
|
border-radius: 3px;
|
}
|
|
&::-webkit-scrollbar-thumb:hover {
|
background-color: rgba(255, 255, 255, 0.5);
|
}
|
}
|
|
.dangerBox .hoverBox {
|
position: absolute;
|
left: 100%;
|
margin-left: 9px;
|
top: 8px;
|
display: none;
|
z-index: 100;
|
|
&::before {
|
content: "";
|
position: absolute;
|
top: 8px;
|
left: 9px;
|
width: 40px;
|
height: 100%;
|
background: transparent;
|
}
|
}
|
|
.dangerBox:hover .hoverBox {
|
display: block;
|
}
|
|
.thisBox .hoverBox {
|
position: absolute;
|
left: 100%;
|
margin-left: 40px;
|
top: -60px;
|
display: none;
|
z-index: 100;
|
|
&::before {
|
content: "";
|
position: absolute;
|
top: 0;
|
left: -40px;
|
width: 40px;
|
height: 100%;
|
background: transparent;
|
}
|
}
|
|
.thisBox:hover .hoverBox {
|
display: block;
|
}
|
|
.searchTabs {
|
background: rgba(2, 0, 77, 0.8);
|
box-shadow: inset 0px 0px 34px 0px rgba(17, 40, 255, 0.66);
|
font-size: 12px;
|
color: rgba(255, 255, 255, 0.8);
|
font-family: "Source Han Sans CN Normal";
|
text-align: center;
|
flex-shrink: 0;
|
|
div {
|
padding: 2px;
|
width: 50px;
|
cursor: pointer;
|
line-height: 24px;
|
border: 1px solid #0d53b7;
|
|
&:hover {
|
background: #017be7;
|
color: #ffffff;
|
}
|
}
|
|
.activeTabs {
|
background: #017be7;
|
color: #ffffff;
|
}
|
}
|
</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>
|