Files
AssetsMap/assetsMap/map.html
2026-05-21 16:40:50 +08:00

609 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>资产地图(聚合版)</title>
<!-- Leaflet -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<!-- MarkerCluster -->
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster/dist/MarkerCluster.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster/dist/MarkerCluster.Default.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet.markercluster/dist/leaflet.markercluster.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<style>
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
}
#map {
height: 100vh;
width: 100%;
}
/* 顶部 */
#topStats {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
}
.statInline {
background: rgba(255, 255, 255, 0.95);
padding: 10px 20px;
border-radius: 24px;
display: flex;
align-items: center;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(6px);
}
.statItem {
font-size: 14px;
color: #666;
display: flex;
align-items: center;
}
.statNumber {
font-size: 18px;
font-weight: bold;
margin-left: 6px;
}
/* 不同颜色 */
.statNumber.total {
color: black;
}
.statNumber.rented {
color: black;
}
.statNumber.vacant {
color: black;
}
/* 分隔线 */
.divider {
width: 1px;
height: 16px;
background: #eee;
margin: 0 12px;
}
/* 搜索 */
.searchInput {
margin-left: 20px;
padding: 6px 10px;
border-radius: 8px;
border: 1px solid #ddd;
width: 280px; /* 这里加长了 */
}
.searchBtn {
margin-left: 8px;
padding: 6px 12px;
background: #27ae60;
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
}
/* 搜索结果 */
.searchPanel {
position: absolute;
top: 65px;
left: 50%;
transform: translateX(5%);
width: 280px;
background: #fff;
border-radius: 10px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
z-index: 9999;
}
.searchItem {
padding: 10px;
border-bottom: 1px solid #eee;
cursor: pointer;
}
.searchItem:hover {
background: #f5f5f5
}
/* 右侧面板 */
#assetPanel {
position: absolute;
top: 0;
right: 0;
width: 360px;
height: 100vh;
background: #fff;
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.15);
transform: translateX(100%);
transition: 0.3s;
display: flex;
flex-direction: column;
z-index: 9999;
}
#assetPanel.show {
transform: translateX(0);
}
.panelHeader {
padding: 15px;
background: #f7f8fa;
border-bottom: 1px solid #eee;
}
.panelTitle {
font-size: 17px;
font-weight: 800;
}
.closeBtn {
float: right;
cursor: pointer;
font-size: 18px;
color: #888;
}
.panelMeta {
padding: 10px 15px;
font-size: 13px;
color: #666;
border-bottom: 1px solid #eee;
}
.assetList {
flex: 1;
overflow: auto;
}
.assetItem {
cursor: pointer;
padding: 14px 16px;
border-bottom: 1px solid #f0f0f0;
}
.marker-cluster-small {
background-color: rgba(231, 76, 60, 0.6) !important;
}
.marker-cluster-small div {
background-color: rgba(231, 76, 60, 0.8) !important;
}
.marker-cluster-medium {
background-color: rgba(231, 76, 60, 0.6) !important;
}
.marker-cluster-medium div {
background-color: rgba(231, 76, 60, 0.8) !important;
}
.marker-cluster-large {
background-color: rgba(192, 57, 43, 0.6) !important;
}
.marker-cluster-large div {
background-color: rgba(192, 57, 43, 0.85) !important;
}
.marker-cluster-custom {
background: transparent !important;
border: none !important;
}
.marker-cluster-custom .cluster-inner {
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(231, 76, 60, 0.85);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.marker-cluster-custom .cluster-inner span {
color: #fff;
font-weight: bold;
font-size: 14px;
}
.assetTenant,
.assetManager,
.assetOwner,
.assetStatus,
.assetPurpose,
.assetAddress,
.assetRemark,
.assetArea {
font-size: 16px;
color: #666;
margin-top: 3px;
}
</style>
</head>
<body>
<div id="app">
<!-- 顶部统计 -->
<!-- 顶部 -->
<div id="topStats">
<div class="statInline">
<div class="statItem">
<span class="statNumber total">总资产: {{allAssetsCount}}</span>
</div>
<div class="divider"></div>
<div class="statItem">
<span class="statNumber rented">已出租: {{rentedCount}}</span>
</div>
<div class="divider"></div>
<div class="statItem">
<span class="statNumber vacant">未出租: {{vacantCount}}</span>
</div>
<!-- 搜索 -->
<input v-model="searchKeyword" @keyup.enter="searchLocation" class="searchInput" placeholder="输入详细地址">
<button @click="searchLocation" class="searchBtn">搜索</button>
</div>
</div>
<!-- 搜索结果 -->
<div v-if="searchResults.length" class="searchPanel">
<div class="searchItem" v-for="(item,index) in searchResults" :key="index"
@click="selectSearchResult(item)">
{{item.assetName || item.address}}
</div>
</div>
<div id="map"></div>
<!-- 右侧资产面板 -->
<div id="assetPanel" :class="{show:panelVisible}">
<div class="panelHeader">
<span class="panelTitle">{{currentRegion.address}}</span>
<span class="closeBtn" @click="closePanel"></span>
</div>
<div class="panelMeta">
资产数量:{{currentRegion.assets ? currentRegion.assets.length : 0}}
</div>
<div class="assetList">
<div class="assetItem" v-for="(a,index) in currentRegion.assets" :key="index" @click="goToDetail(a)">
<div class="assetManager">运营单位:{{a.manager}}</div>
<div class="assetOwner">产权单位:{{a.owner}}</div>
<div class="assetPurpose">用途:{{a.purpose}}</div>
<div class="assetAddress">地址:{{a.address}}</div>
<div class="assetStatus">资产状态:{{a.status || 未知}}</div>
<div class="assetArea">面积:{{a.area}}㎡</div>
<div class="assetRemark">备注:{{a.remark || '暂无'}}</div>
</div>
</div>
</div>
</div>
<script>
new Vue({
el: "#app",
data: {
host: 'http://' + 'www.ycgongxiao.xyz:52100',
searchKeyword: "",
searchResults: [],
searchMarker: null,
map: null,
markerGroup: null,
regions: [],
allAssetsCount: 0,
rentedCount: 0,
vacantCount: 0,
panelVisible: false,
currentRegion: {}
},
mounted() {
this.initMap()
this.queryAssetsRegionsInfo()
this.countAllAssets()
this.countRentedAssets()
this.countRestAssets()
},
watch: {
// 输入清空自动隐藏
searchKeyword(val) {
if (!val) {
this.searchResults = []
}
}
},
methods: {
// 🗺️ 初始化地图
initMap() {
this.map = L.map('map').setView([30.630608, 111.375863], 10)
L.tileLayer('./map/{z}/{x}/{y}/tile.png', {
maxZoom: 18,
minZoom: 8
}).addTo(this.map)
// ⭐ 核心:自定义聚合
this.markerGroup = L.markerClusterGroup({
iconCreateFunction: (cluster) => {
let total = 0
let markers = cluster.getAllChildMarkers()
markers.forEach(m => {
let region = m.options.regionData
total += (region && region.assets ? region.assets.length : 0)
})
return L.divIcon({
html: `
<div class="cluster-inner">
<span>${total}</span>
</div>
`,
className: 'marker-cluster-custom',
iconSize: L.point(40, 40)
})
}
})
this.map.addLayer(this.markerGroup)
// 点击地图关闭弹窗
// 点击地图空白关闭面板
this.map.on('click', () => {
this.searchResults = []
this.panelVisible = false
})
},
countAllAssets() {
fetch(this.host + "/seeyon/rest/assetsRegion/countAssets", {
method: 'GET'
})
.then(res => res.json())
.then(res => {
if (res.code !== 0) {
console.error("接口错误", res.message)
return
}
this.allAssetsCount = res.data || 0
})
.catch(err => {
console.error("加载失败", err)
})
},
countRentedAssets() {
fetch(this.host + "/seeyon/rest/assetsRegion/countAssets?type=1", {
method: 'GET'
})
.then(res => res.json())
.then(res => {
if (res.code !== 0) {
console.error("接口错误", res.message)
return
}
this.rentedCount = res.data || 0
})
.catch(err => {
console.error("加载失败", err)
})
},
countRestAssets() {
fetch(this.host + "/seeyon/rest/assetsRegion/countAssets?type=2", {
method: 'GET'
}).then(res => res.json())
.then(res => {
if (res.code !== 0) {
console.error("接口错误", res.message)
return
}
this.vacantCount = res.data || 0
})
.catch(err => {
console.error("加载失败", err)
})
},
// 📡 请求数据
queryAssetsRegionsInfo() {
fetch(this.host + "/seeyon/rest/assetsRegion/getall", {
method: 'POST'
})
.then(res => res.json())
.then(res => {
if (res.code !== 0) {
console.error("接口错误", res.message)
return
}
this.regions = res.data || []
this.renderMarkers()
})
.catch(err => {
console.error("加载失败", err)
})
},
// 📊 面积计算
calcArea(list) {
let sum = 0
list.forEach(a => {
sum += Number(a.area || 0)
})
return sum
},
// 📍 渲染 marker核心
renderMarkers() {
// 清空旧数据
this.markerGroup.clearLayers()
this.regions.forEach(r => {
let marker = L.marker([r.lat, r.lng])
marker.options.regionData = r
// 点击打开右侧面板
marker.on("click", () => {
this.showAssetPanel(r)
})
// hover提示
marker.on("mouseover", () => {
let area = this.calcArea(r.assets || [])
let owner = r.owner || ""
let manager = r.manager || ""
let count = (r.assets || []).length
let html =
"<b>位置:" + r.address + "</b><br>" +
"运营单位:" + manager + "<br>" +
"产权单位:" + owner + "<br>" +
"面积:" + area + "㎡<br>" +
"资产数量:" + count
marker.bindPopup(html).openPopup()
})
marker.on("mouseout", () => {
marker.closePopup()
})
// ⭐ 加入聚合组
this.markerGroup.addLayer(marker)
})
},
// 搜索
searchLocation() {
if (!this.searchKeyword) return
fetch(this.host + "/seeyon/rest/assetsRegion/search", {
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
keyword: this.searchKeyword
})
})
.then(res => res.json())
.then(res => {
if (res.code !== 0) return
let list = res.data || []
if (!Array.isArray(list)) {
list = [list]
}
this.searchResults = list
// this.selectSearchResult(list[0])
})
},
goToDetail(item) {
let id = item.id
let baseUrl =
`http://www.ycgongxiao.xyz:52100/seeyon/cap4/businessTemplateController.do?v=V10_0SP1_251022_222110_0&method=formContent&type=browse&rightId=-51300951786173064.-7277875919159408408&moduleId=${id}&formTemplateId=3171356881354704902&viewConditionId=&conditionIdDeal=&columnId=3171356881354704902&moduleType=42&isUnFlowDrawer=true&needUpdate=1&tplId=850010&btnId=&comeFrom=list&_v=1776323480999`
window.open(baseUrl)
},
// 选择搜索结果
selectSearchResult(item) {
// 定位
this.map.setView([item.lat, item.lng], 18);
// 移除之前的搜索标记
if (this.searchMarker) {
this.markerGroup.removeLayer(this.searchMarker);
}
// ==============================================
// ✅ 最佳方案:找到原有的那个 marker直接高亮/打开弹窗
// ==============================================
let targetMarker = null;
// 遍历聚合组里所有原有 marker匹配数据
this.markerGroup.eachLayer((layer) => {
if (layer.regionData && layer.regionData.id === item.id) {
targetMarker = layer;
}
});
// 直接打开原有 marker 的弹窗(不新增任何标记)
if (targetMarker) {
targetMarker.fire('mouseover');
return;
}
// 如果没找到(新地点),再创建一个底层标记
// this.searchMarker = L.marker([item.lat, item.lng], {
// zIndexOffset: -1000,
// }).addTo(this.markerGroup);
this.searchResults = []
},
// 📌 打开面板
showAssetPanel(region) {
this.currentRegion = region
this.panelVisible = true
},
// ❌ 关闭面板
closePanel() {
this.panelVisible = false
}
}
})
</script>
</body>
</html>