套索工具(超叼版)

This commit is contained in:
Teo
2025-06-06 20:03:35 +08:00
parent f128217846
commit d6c0d6319f
9 changed files with 665 additions and 44 deletions

View File

@ -14,7 +14,7 @@ VITE_APP_MONITOR_ADMIN = '/admin/applications'
VITE_APP_SNAILJOB_ADMIN = '/snail-job'
# 生产环境
VITE_APP_BASE_API = 'http://192.168.110.5:8899'
VITE_APP_BASE_API = 'http://192.168.110.2:8899'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

View File

@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { ProjectForm, ProjectQuery, ProjectVO } from '@/api/project/project/types';
import { childProjectQuery, ProjectForm, ProjectQuery, ProjectVO } from '@/api/project/project/types';
/**
* 查询项目列表
@ -150,3 +150,26 @@ export const delProject = (id: string | number | Array<string | number>) => {
method: 'delete'
});
};
/**
* 新增子项目
* @param data
*/
export const addChildProject = (data: childProjectQuery) => {
return request({
url: '/project/project/sub',
method: 'post',
data: data
});
};
/**
* 查询项目下的子项目列表
* @param id
*/
export const getChildProject = (id: string | number): AxiosPromise<childProjectQuery[]> => {
return request({
url: '/project/project/list/sub/' + id,
method: 'get'
});
};

View File

@ -128,6 +128,12 @@ export interface locationType {
projectSite: string;
}
export interface childProjectQuery{
projectName:string;
pid:string;
id?:string
}
export interface ProjectForm extends BaseEntity {
/**
*

View File

@ -77,8 +77,10 @@ import { Vector as VectorSource } from 'ol/source'; // OpenLayers的矢量数据
import { Circle, Style, Stroke, Fill, Icon, Text } from 'ol/style'; // OpenLayers的样式类用于定义图层的样式包括圆形样式、基本样式、边框、填充和图标
import LineString from 'ol/geom/LineString'; // OpenLayers的线几何类用于表示线状的地理数据
import Polygon from 'ol/geom/Polygon'; // OpenLayers的多边形几何类用于表示面状的地理数据
import GeoJSON from 'ol/format/GeoJSON';
import * as turf from '@turf/turf';
import { FeatureCollection } from 'geojson';
import { FeatureCollection, Geometry } from 'geojson';
import { MapViewFitter } from '@/utils/setMapCenter';
import { TreeInstance } from 'element-plus';
import { addProjectFacilities, addProjectPilePoint, addProjectSquare, listDXFProject, addInverter, addBoxTransformer } from '@/api/project/project';
import { BatchUploader } from '@/utils/batchUpload';
@ -123,15 +125,21 @@ const jsonData = computed(() => {
});
return arr; // treeData.value;
});
console.log(jsonData);
const handlePosition = (data: any, node) => {
//切换中心点
const featureCollection: FeatureCollection = { type: 'FeatureCollection', features: treeData.value[data.index].features } as FeatureCollection;
const handlePosition = (data: any, node: any) => {
const fitter = new MapViewFitter(map); // 传入你的 OpenLayers 地图实例
const features = treeData.value[data.index]?.features; //features数组
console.log('🚀 ~ handlePosition ~ features:', features);
centerPosition.value = fromLonLat(turf.center(featureCollection).geometry.coordinates);
map.getView().setCenter(centerPosition.value);
if (features?.length) {
const featureCollection: FeatureCollection<Geometry> = {
type: 'FeatureCollection',
features
};
fitter.fit(featureCollection);
}
};
const handleCheckChange = (data: any, bool: boolean) => {
if (isMenuVisible.value) isMenuVisible.value = false;

203
src/utils/lassoSelect.ts Normal file
View File

@ -0,0 +1,203 @@
import { Map as OLMap } from 'ol';
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
import { LineString, Polygon } from 'ol/geom';
import { Feature } from 'ol';
import { Style, Stroke, Fill } from 'ol/style';
import GeoJSON from 'ol/format/GeoJSON';
import { polygon as turfPolygon, booleanIntersects } from '@turf/turf';
import { toLonLat } from 'ol/proj';
import DragPan from 'ol/interaction/DragPan';
import MouseWheelZoom from 'ol/interaction/MouseWheelZoom';
export class LassoSelector {
private map: OLMap;
private drawLayer: VectorLayer<VectorSource>;
private drawSource: VectorSource;
private overlaySource: VectorSource;
private overlayLayer: VectorLayer<VectorSource>;
private drawing = false;
private coordinates: [number, number][] = [];
private targetSource: VectorSource;
private isShiftKeyDown = false;
private onSelectCallback: (selected: Feature[], isInvert?: boolean) => void;
private dragPanInteraction: DragPan | null = null;
private mouseWheelZoomInteraction: MouseWheelZoom | null = null;
constructor(
map: OLMap,
targetSource: VectorSource,
onSelect: (selected: Feature[], isInvert?: boolean) => void
) {
this.map = map;
this.targetSource = targetSource;
this.onSelectCallback = onSelect;
// 找出拖动和滚轮缩放交互
this.dragPanInteraction = this.map
.getInteractions()
.getArray()
.find((interaction) => interaction instanceof DragPan) as DragPan;
this.mouseWheelZoomInteraction = this.map
.getInteractions()
.getArray()
.find((interaction) => interaction instanceof MouseWheelZoom) as MouseWheelZoom;
this.drawSource = new VectorSource();
this.drawLayer = new VectorLayer({
source: this.drawSource,
style: new Style({
stroke: new Stroke({
color: '#ff0000',
width: 2,
}),
}),
});
this.map.addLayer(this.drawLayer);
this.overlaySource = new VectorSource();
this.overlayLayer = new VectorLayer({
source: this.overlaySource,
style: new Style({
stroke: new Stroke({
color: 'rgba(255, 0, 0, 0.8)',
width: 2,
}),
fill: new Fill({
color: 'rgba(255, 0, 0, 0.3)',
}),
}),
});
this.map.addLayer(this.overlayLayer);
this.bindEvents();
}
private bindEvents() {
// 禁用默认右键菜单
this.map.getViewport().addEventListener('contextmenu', (e) => e.preventDefault());
// pointerdown 捕获左键按下
this.map.getViewport().addEventListener('pointerdown', (e) => {
if (e.button === 0 && !this.drawing) {
e.preventDefault();
e.stopPropagation();
this.isShiftKeyDown = e.shiftKey;
this.drawing = true;
this.coordinates = [];
this.drawSource.clear();
this.overlaySource.clear();
// 禁用拖动和缩放
if (this.dragPanInteraction) this.dragPanInteraction.setActive(false);
if (this.mouseWheelZoomInteraction) this.mouseWheelZoomInteraction.setActive(false);
}
});
// pointermove 画线
this.map.on('pointermove', (evt) => {
if (!this.drawing) return;
const coord = evt.coordinate as [number, number];
this.coordinates.push(coord);
this.renderLine();
this.renderPolygon();
});
// pointerup 捕获左键抬起
this.map.getViewport().addEventListener('pointerup', (e) => {
if (e.button === 0 && this.drawing) {
e.preventDefault();
e.stopPropagation();
this.drawing = false;
this.handleDrawEnd();
// 恢复拖动和缩放
if (this.dragPanInteraction) this.dragPanInteraction.setActive(true);
if (this.mouseWheelZoomInteraction) this.mouseWheelZoomInteraction.setActive(true);
}
});
// 防止拖动导致意外事件
this.map.getViewport().addEventListener('pointercancel', (e) => {
if (this.drawing) {
this.drawing = false;
this.drawSource.clear();
this.overlaySource.clear();
if (this.dragPanInteraction) this.dragPanInteraction.setActive(true);
if (this.mouseWheelZoomInteraction) this.mouseWheelZoomInteraction.setActive(true);
}
});
}
private renderLine() {
this.drawSource.clear();
if (this.coordinates.length >= 2) {
const line = new LineString(this.coordinates);
const feature = new Feature({ geometry: line });
this.drawSource.addFeature(feature);
}
}
private renderPolygon() {
this.overlaySource.clear();
if (this.coordinates.length < 3) return;
const polygonCoords = [...this.coordinates, this.coordinates[0]];
const polygon = new Polygon([polygonCoords]);
const feature = new Feature({ geometry: polygon });
this.overlaySource.addFeature(feature);
}
private handleDrawEnd() {
if (this.coordinates.length < 3) {
this.drawSource.clear();
this.overlaySource.clear();
return;
}
const first = this.coordinates[0];
const last = this.coordinates[this.coordinates.length - 1];
if (first[0] !== last[0] || first[1] !== last[1]) {
this.coordinates.push([...first]);
}
const coords4326 = this.coordinates.map((c) => toLonLat(c));
const turfPoly = turfPolygon([coords4326]);
const geojson = new GeoJSON();
const selected: Feature[] = [];
this.targetSource.getFeatures().forEach((feature) => {
const geom = feature.getGeometry();
if (!geom) return;
const geomObj = geojson.writeGeometryObject(geom, {
featureProjection: 'EPSG:3857',
dataProjection: 'EPSG:4326',
}) as any;
if (
(geomObj.type === 'Polygon' || geomObj.type === 'MultiPolygon') &&
booleanIntersects(turfPoly, geomObj)
) {
selected.push(feature);
}
});
if (selected.length) {
this.onSelectCallback(selected, this.isShiftKeyDown);
}
this.drawSource.clear();
this.overlaySource.clear();
}
destroy() {
this.map.removeLayer(this.drawLayer);
this.map.removeLayer(this.overlayLayer);
}
}

58
src/utils/setMapCenter.ts Normal file
View File

@ -0,0 +1,58 @@
// MapViewFitter.ts
import { Map as OlMap } from 'ol';
import GeoJSON from 'ol/format/GeoJSON';
import { FeatureCollection } from 'geojson';
import { bbox as turfBbox, bboxPolygon as turfBboxPolygon } from '@turf/turf';
import type { Geometry } from 'ol/geom';
export class MapViewFitter {
private map: OlMap;
private format: GeoJSON;
constructor(map: OlMap) {
this.map = map;
this.format = new GeoJSON();
}
/**
* 使地图视图自动适应传入的 GeoJSON FeatureCollection 范围
* @param featureCollection GeoJSON FeatureCollection
* @param padding 四周留白,默认为 [10, 10, 10, 10]
* @param duration 动画持续时间,默认为 1000 毫秒
*/
fit(featureCollection: FeatureCollection, padding: number[] = [10, 10, 10, 10], duration: number = 1000) {
if (!featureCollection?.features?.length) return;
const bbox = turfBbox(featureCollection); // [minX, minY, maxX, maxY]
const bboxPolygon = turfBboxPolygon(bbox); // Feature<Polygon>
const geometry: Geometry = this.format.readGeometry(bboxPolygon.geometry, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857'
});
const extent = geometry.getExtent();
this.map.getView().fit(extent, {
padding,
duration
});
}
}
//示例
// import { MapViewFitter } from '@/utils/setMapCenter'; // 确保路径正确
// const fitter = new MapViewFitter(map); // 传入你的 OpenLayers 地图实例
// const features = xxx;//features数组
// if (features?.length) {
// const featureCollection = {
// type: 'FeatureCollection',
// features
// };
// fitter.fit(featureCollection);
// }

View File

@ -6,16 +6,16 @@
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="请选择方阵:" prop="pid" label-width="100">
<!-- <el-input v-model="queryParams.pid" placeholder="请选择" clearable /> -->
<!-- <el-cascader
<el-cascader
:options="matrixOptions"
placeholder="请选择"
@change="handleChange"
:props="{ value: 'id', label: 'matrixName' }"
clearable
/> -->
<el-select v-model="matrixValue" placeholder="请选择" @change="handleChange" clearable>
/>
<!-- <el-select v-model="matrixValue" placeholder="请选择" @change="handleChange" clearable>
<el-option v-for="item in matrixOptions" :key="item.id" :label="item.matrixName" :value="item.id" />
</el-select>
</el-select> -->
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Download" @click="handleQuery">导出周报</el-button>

View File

@ -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>

View File

@ -43,6 +43,36 @@
</template>
<el-table v-loading="loading" :data="projectList" @selection-change="handleSelectionChange">
<el-table-column type="expand" width="50">
<template #default="{ row }">
<div class="w187.25 ml-12.5">
<el-button class="mb" type="primary" size="small" @click="handleOpenSetChild(row.id)" icon="plus">添加子项目</el-button>
<el-table :data="row.children" border stripe>
<el-table-column label="序号" type="index" width="55" align="center" />
<el-table-column label="名称" align="center" prop="projectName" width="296" />
<el-table-column label="创建时间" align="center" prop="createTime" width="199" />
<el-table-column fixed="right" align="center" label="操作" class-name="small-padding fixed-width" width="199">
<template #default="scope">
<el-space>
<el-button
link
type="success"
icon="Edit"
@click="handleOpenSetChild(row.id, scope.row.id, scope.row.projectName)"
v-hasPermi="['project:project:edit']"
>修改
</el-button>
<el-button link type="danger" icon="Delete" @click="handleChildDel(scope.row.id)" v-hasPermi="['project:project:remove']"
>删除
</el-button>
</el-space>
</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="项目名称" align="center" prop="projectName">
@ -278,21 +308,33 @@
@close="polygonStatus = false"
></open-layers-map>
</el-dialog>
<el-dialog title="添加子项目" v-model="childProjectStatus" width="400">
<span>填写子项目名称</span>
<el-input v-model="childProjectForm.projectName"></el-input>
<template #footer>
<span>
<el-button @click="childProjectStatus = false">取消</el-button>
<el-button type="primary" @click="handleSetChild">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup name="Project" lang="ts">
import {
addChildProject,
addProject,
addProjectFacilities,
addProjectPilePoint,
addProjectSquare,
delProject,
getChildProject,
getProject,
listProject,
updateProject
} from '@/api/project/project';
import { ProjectForm, ProjectQuery, ProjectVO, locationType } from '@/api/project/project/types';
import { ProjectForm, ProjectQuery, ProjectVO, childProjectQuery, locationType } from '@/api/project/project/types';
import amap from '@/components/amap/index.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable, project_category_type, project_type } = toRefs<any>(
@ -306,6 +348,7 @@ const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const childProjectStatus = ref(false);
const amapStatus = ref(false);
const queryFormRef = ref<ElFormInstance>();
const projectFormRef = ref<ElFormInstance>();
@ -313,6 +356,11 @@ const polygonStatus = ref(false);
const dxfFile = ref(null);
const projectId = ref<string>('');
const designId = ref<string>('');
const childProjectForm = reactive<childProjectQuery>({
projectName: '',
pid: '',
id: ''
});
//被选中的节点
const nodes = ref<any>([]);
const dialog = reactive<DialogOption>({
@ -563,6 +611,50 @@ const handleDelete = async (row?: ProjectVO) => {
await getList();
};
//删除子项目
const handleChildDel = async (id) => {
await proxy?.$modal.confirm('是否确认删除项目编号为"' + id + '"的数据项?').finally(() => (loading.value = false));
await delProject(id);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
//增加/修改子项目
const handleOpenSetChild = async (pid, id?, name?) => {
childProjectStatus.value = true;
childProjectForm.id = id;
childProjectForm.pid = pid;
childProjectForm.projectName = name;
};
const resetChildQuert = () => {
childProjectForm.id = '';
childProjectForm.pid = '';
childProjectForm.projectName = '';
};
//增加/修改子项目
const handleSetChild = async () => {
if (!childProjectForm.projectName.trim()) return proxy.$modal.msgError('请填写项目名称');
if (childProjectForm.id) {
let res = await updateProject({ id: childProjectForm.id, projectName: childProjectForm.projectName });
if (res.code == 200) {
proxy.$modal.msgSuccess('修改成功');
childProjectStatus.value = false;
resetChildQuert();
getList();
}
} else {
let res = await addChildProject(childProjectForm);
if (res.code == 200) {
proxy.$modal.msgSuccess('添加成功');
childProjectStatus.value = false;
resetChildQuert();
getList();
}
}
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(