套索工具(超叼版)
This commit is contained in:
@ -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
|
||||
|
@ -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'
|
||||
});
|
||||
};
|
@ -128,6 +128,12 @@ export interface locationType {
|
||||
projectSite: string;
|
||||
}
|
||||
|
||||
export interface childProjectQuery{
|
||||
projectName:string;
|
||||
pid:string;
|
||||
id?:string
|
||||
}
|
||||
|
||||
export interface ProjectForm extends BaseEntity {
|
||||
/**
|
||||
*
|
||||
|
@ -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);
|
||||
if (features?.length) {
|
||||
const featureCollection: FeatureCollection<Geometry> = {
|
||||
type: 'FeatureCollection',
|
||||
features
|
||||
};
|
||||
|
||||
map.getView().setCenter(centerPosition.value);
|
||||
fitter.fit(featureCollection);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckChange = (data: any, bool: boolean) => {
|
||||
if (isMenuVisible.value) isMenuVisible.value = false;
|
||||
|
||||
|
203
src/utils/lassoSelect.ts
Normal file
203
src/utils/lassoSelect.ts
Normal 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
58
src/utils/setMapCenter.ts
Normal 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);
|
||||
// }
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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(
|
||||
|
Reference in New Issue
Block a user