Files
AssetsMap/seeyon/apps_res/cap/customeCtrlResources/goMapBtnResources/html/boundConfig.html
2026-06-18 16:26:31 +08:00

499 lines
17 KiB
HTML
Raw 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">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>配置资产边界</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
html, body { height:100%; overflow:hidden; font-family:"Microsoft YaHei",Arial,sans-serif; }
#app { display:flex; height:100%; }
/* 左侧地图 */
#mapWrap { flex:1; position:relative; min-width:0; }
#map { height:100%; width:100%; }
/* 右侧配置面板 */
#configPanel {
width:360px; height:100%; background:#fff;
border-left:1px solid #e0e0e0; display:flex; flex-direction:column;
flex-shrink:0; z-index:10000;
}
.panel-header {
padding:12px 16px; background:#f7f8fa; border-bottom:1px solid #eee;
font-size:14px; font-weight:bold; color:#333; flex-shrink:0;
}
.panel-body {
flex:1; overflow-y:auto; padding:12px 16px;
}
.panel-footer {
padding:10px 16px; border-top:1px solid #eee; display:flex; gap:8px; flex-shrink:0;
}
/* 基本设置 */
.setting-row {
display:flex; align-items:center; gap:8px; margin-bottom:10px;
}
.setting-row label { font-size:12px; color:#888; white-space:nowrap; }
.setting-row input[type="color"] { width:36px; height:30px; border:1px solid #ddd; border-radius:4px; cursor:pointer; }
.setting-row span { font-size:12px; color:#888; }
/* 区域卡片 */
.region-card {
border:1px solid #e0e0e0; border-radius:6px; padding:10px 12px;
margin-bottom:8px; background:#fafafa; cursor:pointer;
}
.region-card:hover { border-color:#27ae60; }
.region-card.selected { border-color:#27ae60; background:#e8f5e9; }
.region-header {
display:flex; justify-content:space-between; align-items:center; margin-bottom:6px;
}
.region-header span { font-size:13px; font-weight:bold; color:#333; }
.region-header .del-btn { color:#e74c3c; cursor:pointer; font-size:16px; padding:0 4px; }
.region-info { font-size:12px; color:#888; }
.hole-info { font-size:11px; color:#e74c3c; margin-top:4px; padding-left:10px; border-left:2px solid #e74c3c; }
/* 按钮 */
.btn {
flex:1; padding:9px 0; border:none; border-radius:6px;
cursor:pointer; font-size:13px; font-weight:bold; transition:all 0.2s;
}
.btn-primary { background:#27ae60; color:#fff; }
.btn-primary:hover { background:#219653; }
.btn-success { background:#3498db; color:#fff; }
.btn-reset { background:#f0f0f0; color:#666; border:1px solid #ddd; }
.btn-danger { background:#e74c3c; color:#fff; }
.tip { font-size:11px; color:#999; margin-top:4px; }
/* 移动端 */
@media (max-width:768px) {
#app { flex-direction:column; }
#mapWrap { height:50%; flex:none; }
#configPanel { width:100%; height:50%; flex:none; border-left:none; border-top:1px solid #e0e0e0; }
}
</style>
</head>
<body>
<div id="app">
<div id="mapWrap"><div id="map"></div></div>
<div id="configPanel">
<div class="panel-header">配置资产区域边界</div>
<div class="panel-body">
<!-- 颜色 -->
<div class="setting-row">
<label>边界颜色:</label>
<input type="color" v-model="color" @change="onColorChange">
<span>{{color}}</span>
</div>
<!-- 区域列表 -->
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
<span style="font-size:13px;font-weight:bold;">区域列表 ({{regions.length}})</span>
<button class="btn btn-primary" style="flex:none;padding:5px 12px;" @click="addRegion">+ 新增区域</button>
</div>
<div v-if="regions.length === 0" style="text-align:center;padding:20px;color:#ccc;">
暂无区域,点击"新增区域"开始
</div>
<div v-for="(r, ri) in regions" :key="r.id"
class="region-card" :class="{selected: selectedRegionIndex === ri}"
@click="selectRegionCard(ri)">
<div class="region-header">
<span>区域 {{ri + 1}} <span style="font-size:11px;color:#aaa;">ID:{{r.id}}</span></span>
<span class="del-btn" @click.stop="removeRegion(ri)">&times;</span>
</div>
<div class="region-info">
外圈:{{r.outer ? r.outer.length : 0}} 个点
<span v-if="r.holes && r.holesCount > 0"> 孔洞:{{r.holesCount}} 个</span>
</div>
<div v-if="r.holes && r.holesCount > 0" class="hole-info">
孔洞共 {{r.holesCount}} 个
</div>
<div style="display:flex;gap:4px;margin-top:6px;">
<button class="btn" style="flex:none;padding:3px 8px;font-size:11px;background:#9b59b6;color:#fff;border:none;border-radius:4px;cursor:pointer;" @click.stop="startDrawHole(ri)">挖洞</button>
<button v-if="r.holes && r.holesCount > 0" class="btn" style="flex:none;padding:3px 8px;font-size:11px;background:#e67e22;color:#fff;border:none;border-radius:4px;cursor:pointer;" @click.stop="clearHoles(ri)">清除孔洞</button>
</div>
</div>
<div class="tip">提示:点击区域卡片选中后,可在地图上看到高亮显示</div>
</div>
<div class="panel-footer">
<button class="btn btn-primary" @click="confirmSave">确认保存</button>
<button class="btn btn-reset" @click="cancelClose">取消</button>
</div>
</div>
</div>
<script>
// 从父页面获取已有边界数据
var oldBoundConfig = '';
var assetsId = '';
var _host = '';
try {
var params = window.parentDialogObj['boundConfigDialog'].getTransParams();
console.log(window.parentDialogObj['boundConfigDialog'])
console.log('[boundConfig] 父页面传入的参数:', JSON.stringify(params));
oldBoundConfig = params.oldBoundConfig || '';
assetsId = params.assetsId || params.assetId || params.assetsNo || '';
} catch(e) {
console.warn('获取父页面参数异常:', e);
}
// 尝试从父页面URL推断host
try {
_host = window.parent.location.origin || '';
} catch(e) {}
console.log('[boundConfig] oldBoundConfig:', oldBoundConfig ? (oldBoundConfig.substring(0, 100) + '...') : '(空)');
console.log('[boundConfig] assetsId:', assetsId);
new Vue({
el: '#app',
data: {
map: null,
drawnItems: null,
color: '#ff0000',
regions: [],
assetsId: assetsId,
selectedRegionIndex: -1,
_drawingMode: null // 'new' | 'hole'
},
mounted() {
this.initMap();
},
methods: {
initMap() {
console.log('[boundConfig] initMap, assetsId:', assetsId);
this.map = L.map('map').setView([30.2856, 109.4783], 13);
L.tileLayer('http://localhost:3080/seeyon/seeyonExtend/assetsMap/map/{z}/{x}/{y}/tile.png', { maxZoom: 18, minZoom: 8 }).addTo(this.map);
this.drawnItems = L.featureGroup().addTo(this.map);
this.markerGroup = L.layerGroup().addTo(this.map);
this.markerIcon = L.divIcon({
html: '<img src="./markicon.png" style="width:48px;height:48px;" onerror="this.style.display=\'none\';this.parentNode.innerHTML=\'<div style=width:24px;height:24px;background:#e74c3c;border-radius:50%;border:3px solid #fff;box-shadow:0 0 4px rgba(0,0,0,0.4);></div>\';">',
className: 'custom-marker-icon',
iconSize: [48, 48],
iconAnchor: [24, 24]
});
// 绘制控件
var self = this;
this.drawControl = new L.Control.Draw({
draw: {
polygon: {
allowIntersection: false,
shapeOptions: { color: self.color, weight: 2, fillOpacity: 0.3 }
},
polyline: false, rectangle: false, circle: false, circlemarker: false, marker: false
},
edit: { featureGroup: self.drawnItems, remove: true }
});
this.map.addControl(this.drawControl);
// 同步颜色到绘制控件
this.syncDrawColor();
// 绘制完成事件
this.map.on(L.Draw.Event.CREATED, function(e) {
var layer = e.layer;
var latlngs = layer.getLatLngs()[0].map(function(ll) { return [ll.lat, ll.lng]; });
if (self._drawingMode === 'hole' && self.selectedRegionIndex >= 0 && self.selectedRegionIndex < self.regions.length) {
// 挖洞:将绘制的多边形作为孔洞添加到选中的区域
var region = self.regions[self.selectedRegionIndex];
if (!region.holes) region.holes = [];
region.holes.push(latlngs);
region.holesCount = region.holes.length;
} else if (self._pendingRegionIndex >= 0 && self._pendingRegionIndex < self.regions.length) {
self.regions[self._pendingRegionIndex].outer = latlngs;
} else {
self.regions.push({ id: self.generateId(), outer: latlngs, holes: [], holesCount: 0 });
self.selectedRegionIndex = self.regions.length - 1;
}
self._drawingMode = null;
self._pendingRegionIndex = -1;
self.refreshMap();
});
// 编辑完成事件
this.map.on(L.Draw.Event.EDITED, function(e) {
var layers = e.layers;
layers.eachLayer(function(layer) {
var latlngs = layer.getLatLngs();
var idx = self.findRegionByLayer(layer);
if (idx >= 0) {
self.regions[idx].outer = latlngs[0].map(function(ll) { return [ll.lat, ll.lng]; });
// 保留孔洞
var holes = [];
for (var i = 1; i < latlngs.length; i++) {
holes.push(latlngs[i].map(function(ll) { return [ll.lat, ll.lng]; }));
}
self.regions[idx].holes = holes;
self.regions[idx].holesCount = holes.length;
}
});
self.refreshMap();
});
// 删除事件
this.map.on(L.Draw.Event.DELETED, function(e) {
// 删除对应 region
e.layers.eachLayer(function(layer) {
var idx = self.findRegionByLayer(layer);
if (idx >= 0) {
self.regions.splice(idx, 1);
}
});
if (self.selectedRegionIndex >= self.regions.length) {
self.selectedRegionIndex = self.regions.length - 1;
}
self.refreshMap();
});
this._pendingRegionIndex = -1;
setTimeout(function() {
self.map.invalidateSize();
self.loadAssetLocation();
self.loadBoundData();
}, 200);
},
findRegionByLayer(layer) {
if (layer._regionIndex !== undefined && layer._regionIndex >= 0) {
return layer._regionIndex;
}
var latlngs = layer.getLatLngs()[0];
if (!latlngs || latlngs.length === 0) return -1;
var firstPt = [latlngs[0].lat, latlngs[0].lng];
for (var i = 0; i < this.regions.length; i++) {
var r = this.regions[i];
if (r.outer && r.outer.length > 0 &&
Math.abs(r.outer[0][0] - firstPt[0]) < 0.0001 &&
Math.abs(r.outer[0][1] - firstPt[1]) < 0.0001) {
return i;
}
}
return -1;
},
loadBoundData() {
console.log('[boundConfig] loadBoundData, oldBoundConfig:', oldBoundConfig);
if (!oldBoundConfig) {
console.log('[boundConfig] oldBoundConfig为空跳过');
return;
}
try {
var bound = typeof oldBoundConfig === 'string' ? JSON.parse(oldBoundConfig) : oldBoundConfig;
console.log('[boundConfig] 解析后的边界数据:', bound);
this.color = bound.color || '#ff0000';
this.syncDrawColor();
this.regions = this.normalizeBoundary(bound);
console.log('[boundConfig] regions数量:', this.regions.length);
this.refreshMap();
} catch(e) {
console.error('[boundConfig] 解析边界数据失败:', e, '原始数据:', oldBoundConfig);
}
},
// 根据 assetsId资产编号调接口获取经纬度在地图上标点
loadAssetLocation() {
if (!assetsId) {
console.log('[boundConfig] assetsId为空跳过标点');
return;
}
var self = this;
var url = _host + '/seeyon/rest/assetsRegion/getByBounds';
console.log('[boundConfig] 请求资产位置, url:', url, 'assetId:', assetsId);
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ assetId: assetsId })
}).then(function(r) {
console.log('[boundConfig] 响应状态:', r.status);
return r.json();
}).then(function(d) {
console.log('[boundConfig] 资产位置返回:', d);
if (d.code === 0 && d.data && d.data.length > 0) {
var region = d.data[0];
var lat = parseFloat(region.lat);
var lng = parseFloat(region.lng);
console.log('[boundConfig] 经纬度:', lat, lng);
if (!isNaN(lat) && !isNaN(lng)) {
var m = L.marker([lat, lng], { icon: self.markerIcon });
m.bindPopup(
'<b>' + (region.assets && region.assets[0] ? region.assets[0].assetName : assetsId) + '</b><br>' +
'坐标:' + lat + ', ' + lng,
{ offset: [0, -24] }
);
m.addTo(self.markerGroup);
self.map.setView([lat, lng], 16);
setTimeout(function() { m.openPopup(); }, 300);
}
} else {
console.warn('[boundConfig] 未查到资产位置数据, code:', d.code, 'data:', d.data);
}
}).catch(function(err) {
console.error('[boundConfig] 请求资产位置失败:', err);
});
},
normalizeBoundary(bound) {
if (!bound) return [];
var self = this;
if (bound.regions && Array.isArray(bound.regions)) {
return bound.regions.map(function(r) {
var holes = r.holes || [];
return {
id: r.id || self.generateId(),
outer: r.outer || [],
holes: holes,
holesCount: holes.length
};
});
}
if (!bound.polygon || bound.polygon.length === 0) return [];
var polygon = bound.polygon;
if (typeof polygon[0][0] === 'number') {
return [{ id: self.generateId(), outer: polygon, holes: [], holesCount: 0 }];
}
if (typeof polygon[0][0][0] === 'number') {
return [{ id: self.generateId(), outer: polygon[0], holes: polygon.slice(1), holesCount: polygon.length - 1 }];
}
return polygon.map(function(r) {
if (typeof r[0][0] === 'number') {
return { id: self.generateId(), outer: r, holes: [], holesCount: 0 };
}
var h = r.slice(1);
return { id: self.generateId(), outer: r[0], holes: h, holesCount: h.length };
});
},
generateId() {
return Date.now() * 1000 + Math.floor(Math.random() * 1000);
},
onColorChange() {
this.syncDrawColor();
this.refreshMap();
},
syncDrawColor() {
// 重建绘制控件以同步颜色
if (this.drawControl) {
this.map.removeControl(this.drawControl);
}
var self = this;
this.drawControl = new L.Control.Draw({
draw: {
polygon: {
allowIntersection: false,
shapeOptions: { color: self.color, fillColor: self.color, weight: 2, fillOpacity: 0.3 }
},
polyline: false, rectangle: false, circle: false, circlemarker: false, marker: false
},
edit: { featureGroup: self.drawnItems, remove: true }
});
this.map.addControl(this.drawControl);
},
refreshMap() {
this.drawnItems.clearLayers();
var self = this;
this.regions.forEach(function(region, idx) {
if (!region.outer || region.outer.length < 3) return;
var rings = [region.outer].concat(region.holes || []);
var isSelected = (idx === self.selectedRegionIndex);
var polygon = L.polygon(rings, {
color: isSelected ? '#27ae60' : self.color,
fillColor: isSelected ? '#27ae60' : self.color,
weight: isSelected ? 4 : 2,
fillOpacity: isSelected ? 0.5 : 0.3
});
polygon._regionIndex = idx;
self.drawnItems.addLayer(polygon);
});
if (this.drawnItems.getLayers().length > 0) {
this.map.fitBounds(this.drawnItems.getBounds(), { padding: [30, 30] });
}
},
selectRegionCard(index) {
this.selectedRegionIndex = index;
this.refreshMap();
},
addRegion() {
// 直接触发多边形绘制,绘制完成后再添加到 regions
this._pendingRegionIndex = -1;
this._drawingMode = 'new';
new L.Draw.Polygon(this.map, {
shapeOptions: { color: this.color, weight: 2, fillOpacity: 0.3 },
allowIntersection: false
}).enable();
},
startDrawHole(index) {
if (!this.regions[index] || !this.regions[index].outer || this.regions[index].outer.length < 3) {
alert('请先绘制外圈边界!');
return;
}
this.selectedRegionIndex = index;
this._drawingMode = 'hole';
new L.Draw.Polygon(this.map, {
shapeOptions: { color: '#9b59b6', weight: 2, fillOpacity: 0.3 },
allowIntersection: false
}).enable();
},
clearHoles(index) {
if (this.regions[index]) {
this.regions[index].holes = [];
this.regions[index].holesCount = 0;
this.refreshMap();
}
},
removeRegion(index) {
this.regions.splice(index, 1);
if (this.selectedRegionIndex >= this.regions.length) {
this.selectedRegionIndex = this.regions.length - 1;
}
this.refreshMap();
},
confirmSave() {
var cleanRegions = this.regions
.filter(function(r) { return r.outer && r.outer.length >= 3; })
.map(function(r) {
return {
id: r.id,
outer: r.outer,
holes: (r.holes || []).filter(function(h) { return h && h.length >= 3; })
};
});
var result = JSON.stringify({ regions: cleanRegions, color: this.color });
// 设置返回值供父页面获取
window._boundConfigResult = result;
alert('数据已生成,请点击"确认保存"按钮完成保存');
},
cancelClose() {
window._boundConfigResult = '';
}
}
});
function OK() {
// 弹框框架调用此函数获取返回值
return window._boundConfigResult || '';
}
</script>
</body>
</html>