套索工具(超叼版)
This commit is contained in:
@ -1,7 +1,15 @@
|
||||
<template>
|
||||
<div class="header flex justify-end">
|
||||
<div class="header flex justify-between">
|
||||
<div class="tips flex items-center justify-between">
|
||||
<div class="dot1">未提交</div>
|
||||
<div class="dot2">已提交</div>
|
||||
<div class="dot3">已选择,待提交</div>
|
||||
<el-tooltip class="box-item" effect="dark" content="右键拖动生成套索区域选中/取消图形" placement="bottom">
|
||||
<i class="iconfont icon-wenhao"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-form :model="queryParams" ref="form" label-width="80px" inline class="flex items-center">
|
||||
<el-form-item label="请选择项目:" prop="pid" label-width="100" style="margin-bottom: 0">
|
||||
<el-form-item label="请选择项目:" prop="pid" label-width="100" style="margin-bottom: 0; color: #fff">
|
||||
<el-select v-model="selectedProjectId" placeholder="请选择" @change="handleSelect" clearable>
|
||||
<el-option v-for="item in ProjectList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
@ -45,15 +53,20 @@ import { useUserStoreHook } from '@/store/modules/user';
|
||||
import { getProjectSquare, listProgressCategory, addDaily, workScheduleListPosition } from '@/api/progress/plan';
|
||||
import { ProgressCategoryVO, progressPlanDetailForm } from '@/api/progress/plan/types';
|
||||
import { Circle, Fill, Stroke, Style, Text } from 'ol/style';
|
||||
import { defaults as defaultInteractions } from 'ol/interaction';
|
||||
import { defaults as defaultInteractions, DragPan } from 'ol/interaction';
|
||||
import Feature from 'ol/Feature';
|
||||
import { Point, Polygon } from 'ol/geom';
|
||||
import VectorSource from 'ol/source/Vector';
|
||||
import VectorLayer from 'ol/layer/Vector';
|
||||
import Node from 'element-plus/es/components/tree/src/model/node.mjs';
|
||||
import { ElCheckbox } from 'element-plus';
|
||||
import { LassoSelector } from '@/utils/lassoSelect';
|
||||
import { FeatureCollection, Geometry } from 'geojson';
|
||||
import { MapViewFitter } from '@/utils/setMapCenter';
|
||||
import PointerInteraction from 'ol/interaction/Pointer';
|
||||
import { Coordinate } from 'ol/coordinate';
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const selector = ref<LassoSelector | null>(null);
|
||||
// 获取用户 store
|
||||
const userStore = useUserStoreHook();
|
||||
// 从 store 中获取项目列表和当前选中的项目
|
||||
@ -122,7 +135,14 @@ const handleCheckChange = (data: any, checked: boolean, indeterminate: boolean)
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!checked) return (submitForm.value.id = ''); // 只处理第三级节点的选中事件
|
||||
// if (!checked) return (submitForm.value.id = ''); // 只处理第三级节点的选中事件
|
||||
if (!checked) {
|
||||
// ✅ 只在取消的是当前 id 时清空
|
||||
if (submitForm.value.id === data.id) {
|
||||
submitForm.value.id = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const parent = node.parent;
|
||||
if (!parent) return;
|
||||
@ -131,6 +151,7 @@ const handleCheckChange = (data: any, checked: boolean, indeterminate: boolean)
|
||||
// 设置当前点击项为选中
|
||||
treeRef.value.setChecked(data.id, true, false);
|
||||
submitForm.value.id = data.id; // 设置提交表单的id
|
||||
console.log('submitForm', submitForm.value);
|
||||
};
|
||||
|
||||
//清除某一级节点所有选中状态
|
||||
@ -213,6 +234,7 @@ const submit = () => {
|
||||
console.log('sunbmitForm', submitForm.value);
|
||||
const { finishedDetailIdList, id } = submitForm.value;
|
||||
if (!id || finishedDetailIdList.length === 0) return proxy?.$modal.msgWarning('请选择图层以及日期');
|
||||
|
||||
loading.value = true;
|
||||
addDaily(submitForm.value)
|
||||
.then(() => {
|
||||
@ -267,7 +289,7 @@ const renderContent = (context, { node, data }) => {
|
||||
style: 'margin-right: 8px;',
|
||||
disabled: data.disabled
|
||||
}),
|
||||
h('span', node.label)
|
||||
h('span', [node.label])
|
||||
]);
|
||||
}
|
||||
if (node.level === 2) {
|
||||
@ -278,13 +300,58 @@ const renderContent = (context, { node, data }) => {
|
||||
style: 'margin-right: 8px;',
|
||||
disabled: !data.threeChildren || data.threeChildren.length == 0
|
||||
}),
|
||||
h('span', node.label)
|
||||
h('span', { onDblclick: () => handlePosition(data, node) }, node.label)
|
||||
]);
|
||||
} else {
|
||||
return h('span', node.label);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePosition = (data: any, node: any) => {
|
||||
if (!data.threeChildren) return;
|
||||
|
||||
const fitter = new MapViewFitter(map); // 传入你的 OpenLayers 地图实例
|
||||
const features: GeoJSON.Feature[] = data.threeChildren.map((item) => {
|
||||
if ('type' in item && item.type === 'Feature') {
|
||||
return item as GeoJSON.Feature;
|
||||
}
|
||||
const raw = item;
|
||||
|
||||
let coordinates: any;
|
||||
if (raw.type === 'Polygon') {
|
||||
coordinates = [(raw.positions as [string, string][]).map(([lng, lat]) => [parseFloat(lng), parseFloat(lat)])];
|
||||
} else if (raw.type === 'Point') {
|
||||
const [lng, lat] = raw.positions as [string, string];
|
||||
coordinates = [parseFloat(lng), parseFloat(lat)];
|
||||
} else {
|
||||
throw new Error(`Unsupported geometry type: ${raw.type}`);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: raw.type,
|
||||
coordinates
|
||||
},
|
||||
properties: {
|
||||
id: raw.id,
|
||||
name: raw.name,
|
||||
status: raw.status,
|
||||
finishDate: raw.finishDate
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if (features?.length) {
|
||||
const featureCollection: FeatureCollection<Geometry> = {
|
||||
type: 'FeatureCollection',
|
||||
features
|
||||
};
|
||||
|
||||
fitter.fit(featureCollection);
|
||||
}
|
||||
};
|
||||
|
||||
//切换项目重置方阵
|
||||
const resetMatrix = () => {
|
||||
matrixValue.value = undefined;
|
||||
@ -330,7 +397,13 @@ const initOLMap = () => {
|
||||
new TileLayer({
|
||||
// 设置图层的数据源为XYZ类型。XYZ是一个通用的瓦片图层源,它允许你通过URL模板来获取瓦片
|
||||
source: new XYZ({
|
||||
url: 'https://webrd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}'
|
||||
url: 'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}'
|
||||
})
|
||||
}),
|
||||
new TileLayer({
|
||||
// 设置图层的数据源为XYZ类型。XYZ是一个通用的瓦片图层源,它允许你通过URL模板来获取瓦片
|
||||
source: new XYZ({
|
||||
url: 'http://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8'
|
||||
})
|
||||
})
|
||||
],
|
||||
@ -360,20 +433,7 @@ const initOLMap = () => {
|
||||
const zoom = map.getView().getZoom();
|
||||
const scale = Math.max(zoom / 10, 1); // 缩放比例,根据需要调整公式
|
||||
map.forEachFeatureAtPixel(e.pixel, (feature: Feature) => {
|
||||
const geomType = feature.getGeometry().getType();
|
||||
if (feature.get('status') === '2' || geomType != 'Polygon') return; // 如果是完成状态,直接返回
|
||||
|
||||
const isHighlighted = feature.get('highlighted') === true;
|
||||
|
||||
if (isHighlighted) {
|
||||
feature.setStyle(defaultStyle(feature.get('name'), scale)); // 清除高亮样式
|
||||
feature.set('highlighted', false);
|
||||
submitForm.value.finishedDetailIdList = submitForm.value.finishedDetailIdList.filter((id) => id !== feature.get('id')); // 从已完成列表中移除
|
||||
return;
|
||||
}
|
||||
feature.setStyle(highlightStyle(feature.get('name'), scale));
|
||||
feature.set('highlighted', true);
|
||||
submitForm.value.finishedDetailIdList.push(feature.get('id')); // 添加到已完成列表
|
||||
toggleFeatureHighlight(feature);
|
||||
});
|
||||
});
|
||||
map.getView().on('change:resolution', () => {
|
||||
@ -389,6 +449,58 @@ const initOLMap = () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 4. 添加 pointermove 鼠标悬停事件
|
||||
let lastFeature: Feature | null = null;
|
||||
|
||||
map.on('pointermove', (evt) => {
|
||||
map.getTargetElement().style.cursor = '';
|
||||
|
||||
const feature = map.forEachFeatureAtPixel(evt.pixel, (f) => f);
|
||||
const zoom = map.getView().getZoom();
|
||||
const scale = Math.max(zoom / 10, 1);
|
||||
|
||||
// 👉 若当前划入的 feature 是不允许 hover 的,直接跳过处理
|
||||
const currStatus = feature?.get('status');
|
||||
const currHighlighted = feature?.get('highlighted');
|
||||
if (feature && (currStatus === '2' || currHighlighted === true || feature.getGeometry()?.getType() !== 'Polygon')) {
|
||||
return; // ❌ 不执行 hover 效果,也不更新 lastFeature
|
||||
}
|
||||
|
||||
// ✅ 若进入了新的可 hover feature
|
||||
if (feature && feature !== lastFeature) {
|
||||
if (lastFeature) {
|
||||
const lastStatus = lastFeature.get('status');
|
||||
const lastHighlighted = lastFeature.get('highlighted');
|
||||
|
||||
if (lastStatus === '2') {
|
||||
lastFeature.setStyle(successStyle(lastFeature.get('name'), scale));
|
||||
} else if (lastHighlighted === true) {
|
||||
lastFeature.setStyle(highlightStyle(lastFeature.get('name'), scale));
|
||||
} else {
|
||||
lastFeature.setStyle(defaultStyle(lastFeature.get('name'), scale));
|
||||
}
|
||||
}
|
||||
|
||||
feature.setStyle(hoverStyle(feature.get('name'), scale));
|
||||
map.getTargetElement().style.cursor = 'pointer';
|
||||
lastFeature = feature;
|
||||
} else if (!feature && lastFeature) {
|
||||
// ✅ 鼠标移出所有图形时恢复
|
||||
const lastStatus = lastFeature.get('status');
|
||||
const lastHighlighted = lastFeature.get('highlighted');
|
||||
|
||||
if (lastStatus === '2') {
|
||||
lastFeature.setStyle(successStyle(lastFeature.get('name'), scale));
|
||||
} else if (lastHighlighted === true) {
|
||||
lastFeature.setStyle(highlightStyle(lastFeature.get('name'), scale));
|
||||
} else {
|
||||
lastFeature.setStyle(defaultStyle(lastFeature.get('name'), scale));
|
||||
}
|
||||
|
||||
lastFeature = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const highlightStyle = (name, scale) => {
|
||||
@ -401,13 +513,13 @@ const highlightStyle = (name, scale) => {
|
||||
color: 'rgba(255,165,0,0.3)' // 半透明橙色
|
||||
}),
|
||||
text: new Text({
|
||||
font: '14px Microsoft YaHei',
|
||||
font: '10px Microsoft YaHei',
|
||||
text: name,
|
||||
placement: 'line', // 👈 关键属性
|
||||
offsetX: 50, // 向右偏移 10 像素
|
||||
offsetY: 20, // 向下偏移 5 像素
|
||||
scale,
|
||||
fill: new Fill({ color: 'orange' })
|
||||
fill: new Fill({ color: '#FFFFFF' })
|
||||
})
|
||||
});
|
||||
};
|
||||
@ -419,13 +531,13 @@ const defaultStyle = (name, scale) => {
|
||||
width: 2
|
||||
}),
|
||||
text: new Text({
|
||||
font: '12px Microsoft YaHei',
|
||||
font: '10px Microsoft YaHei',
|
||||
text: name,
|
||||
scale,
|
||||
placement: 'line', // 👈 关键属性
|
||||
offsetX: 50, // 向右偏移 10 像素
|
||||
offsetY: 20, // 向下偏移 5 像素
|
||||
fill: new Fill({ color: '#003366 ' })
|
||||
fill: new Fill({ color: '#FFFFFF ' })
|
||||
}),
|
||||
fill: new Fill({ color: 'skyblue' })
|
||||
});
|
||||
@ -438,7 +550,7 @@ const successStyle = (name, scale) => {
|
||||
width: 2
|
||||
}),
|
||||
text: new Text({
|
||||
font: '14px Microsoft YaHei',
|
||||
font: '10px Microsoft YaHei',
|
||||
text: name,
|
||||
scale,
|
||||
placement: 'line', // 👈 关键属性
|
||||
@ -450,6 +562,27 @@ const successStyle = (name, scale) => {
|
||||
});
|
||||
};
|
||||
|
||||
const hoverStyle = (name, scale) => {
|
||||
return new Style({
|
||||
stroke: new Stroke({
|
||||
color: 'orange',
|
||||
width: 2
|
||||
}),
|
||||
fill: new Fill({
|
||||
color: 'rgba(255,165,0,0.3)' // 半透明橙色
|
||||
}),
|
||||
text: new Text({
|
||||
font: '10px Microsoft YaHei',
|
||||
text: name,
|
||||
scale,
|
||||
placement: 'line', // 👈 关键属性
|
||||
offsetX: 50, // 向右偏移 10 像素
|
||||
offsetY: 20, // 向下偏移 5 像素
|
||||
fill: new Fill({ color: '#FFFFFF ' })
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建图层
|
||||
* @param {*} pointObj 坐标数组
|
||||
@ -477,7 +610,7 @@ const creatPoint = (pointObj: Array<any>, type: string, id: string, name?: strin
|
||||
|
||||
const feature = new Feature({ geometry });
|
||||
const zoom = map.getView().getZoom();
|
||||
const scale = Math.max(zoom / 10, 1); // 缩放比例,根据需要调整公式
|
||||
const scale = Math.max(zoom / 30, 1); // 缩放比例,根据需要调整公式
|
||||
const pointStyle = new Style({
|
||||
image: new Circle({
|
||||
radius: 2,
|
||||
@ -519,15 +652,77 @@ const addPointToMap = (features: Array<any>) => {
|
||||
});
|
||||
};
|
||||
|
||||
//选中几何图形
|
||||
const toggleFeatureHighlight = (feature: Feature, addIfNotExist = true) => {
|
||||
const zoom = map.getView().getZoom();
|
||||
const scale = Math.max(zoom / 10, 1);
|
||||
if (feature.get('status') === '2') return;
|
||||
if (feature.getGeometry()?.getType() !== 'Polygon') return;
|
||||
|
||||
const isHighlighted = feature.get('highlighted') === true;
|
||||
|
||||
if (isHighlighted) {
|
||||
feature.setStyle(defaultStyle(feature.get('name'), scale));
|
||||
feature.set('highlighted', false);
|
||||
const id = feature.get('id');
|
||||
const idx = submitForm.value.finishedDetailIdList.indexOf(id);
|
||||
if (idx > -1) submitForm.value.finishedDetailIdList.splice(idx, 1);
|
||||
} else if (addIfNotExist) {
|
||||
feature.setStyle(highlightStyle(feature.get('name'), scale));
|
||||
feature.set('highlighted', true);
|
||||
const id = feature.get('id');
|
||||
if (!submitForm.value.finishedDetailIdList.includes(id)) submitForm.value.finishedDetailIdList.push(id);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 地图初始化
|
||||
initOLMap();
|
||||
map.addLayer(sharedLayer);
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
selector.value = new LassoSelector(map, sharedSource, (features, isInvert = false) => {
|
||||
features.forEach((feature) => {
|
||||
if (isInvert) {
|
||||
// Shift + 左键 -> 只执行取消选中
|
||||
if (feature.get('highlighted') === true) {
|
||||
toggleFeatureHighlight(feature, false); // 取消选中,addIfNotExist = false
|
||||
}
|
||||
} else {
|
||||
// 普通左键 -> 只执行选中
|
||||
if (feature.get('highlighted') !== true) {
|
||||
toggleFeatureHighlight(feature, true); // 选中,addIfNotExist = true
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
enableMiddleMousePan(map);
|
||||
getList();
|
||||
});
|
||||
|
||||
function enableMiddleMousePan(map: Map) {
|
||||
// 先移除默认的 DragPan(通常响应左键)
|
||||
const interactions = map.getInteractions();
|
||||
interactions.forEach((interaction) => {
|
||||
if (interaction instanceof DragPan) {
|
||||
map.removeInteraction(interaction);
|
||||
}
|
||||
});
|
||||
|
||||
// 添加只响应中键的 DragPan
|
||||
const middleButtonDragPan = new DragPan({
|
||||
condition: (event) => {
|
||||
// 只允许中键 (mouse button 1) 拖动
|
||||
return event.originalEvent instanceof MouseEvent && event.originalEvent.button === 1;
|
||||
}
|
||||
});
|
||||
|
||||
map.addInteraction(middleButtonDragPan);
|
||||
|
||||
// 禁用中键点击默认滚动行为(浏览器可能会出现滚动箭头)
|
||||
map.getViewport().addEventListener('mousedown', (e) => {
|
||||
if (e.button === 1) e.preventDefault();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -566,4 +761,40 @@ onMounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.tips {
|
||||
margin: 0 15px;
|
||||
position: relative;
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
> div {
|
||||
margin: 0 25px;
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
left: -15px;
|
||||
top: 30%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
.dot1 {
|
||||
&::before {
|
||||
background-color: #1d6fe9;
|
||||
}
|
||||
}
|
||||
.dot2 {
|
||||
&::before {
|
||||
background-color: #67c23a;
|
||||
}
|
||||
}
|
||||
.dot3 {
|
||||
&::before {
|
||||
background-color: #ff8d1a;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user