项目列表上传DXF文件并显示

This commit is contained in:
Teo
2025-04-23 18:19:47 +08:00
parent 68648072de
commit 0ab7056dd1
8 changed files with 326 additions and 78 deletions

View File

@ -22,6 +22,7 @@
"dependencies": {
"@element-plus/icons-vue": "2.3.1",
"@highlightjs/vue-plugin": "2.1.0",
"@turf/turf": "^7.2.0",
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "11.3.0",
"animate.css": "4.1.1",
@ -38,6 +39,7 @@
"highlight.js": "11.9.0",
"image-conversion": "2.1.1",
"js-cookie": "3.0.5",
"js-md5": "^0.8.3",
"jsencrypt": "3.3.2",
"mitt": "^3.0.1",
"nprogress": "0.2.0",

View File

@ -1,11 +1,6 @@
<template loading="true">
<el-config-provider :locale="appStore.locale" :size="appStore.size">
<router-view />
<div>123</div>
<div v-for="item in aarr">
1<span v-for="itm in item.features">{{ itm.geometry.coordinates }}1</span>
</div>
<el-button type="primary" size="default" @click="a" class="ml-100" v-loading.fullscreen.lock="fullscreenLoading">123</el-button>
</el-config-provider>
</template>
@ -14,22 +9,7 @@ import useSettingsStore from '@/store/modules/settings';
import { handleThemeStyle } from '@/utils/theme';
import useAppStore from '@/store/modules/app';
import { getProjectTeam } from './utils/projectTeam';
import request from '@/utils/request';
const appStore = useAppStore();
const fullscreenLoading = ref(false);
const aarr = ref([]);
const a = () => {
fullscreenLoading.value = true;
request({
url: '/project/project/json',
method: 'get'
}).then((res) => {
console.log(res);
aarr.value = res.data.layers;
fullscreenLoading.value = false;
});
};
onMounted(() => {
nextTick(() => {
@ -39,3 +19,20 @@ onMounted(() => {
});
});
</script>
<style>
* {
-webkit-touch-callout: none; /*系统默认菜单被禁用*/
-webkit-user-select: none; /*webkit浏览器*/
-khtml-user-select: none; /*早期浏览器*/
-moz-user-select: none; /*火狐*/
-ms-user-select: none; /*IE10*/
user-select: none;
}
input {
-webkit-user-select: auto; /*webkit浏览器*/
}
textarea {
-webkit-user-select: auto; /*webkit浏览器*/
}
</style>

View File

@ -16,6 +16,19 @@ export const listProject = (query?: ProjectQuery): AxiosPromise<ProjectVO[]> =>
});
};
/**
* 查询项目dxf
* @param query
* @returns {*}
*/
export const listDXFProject = (id: string | number): AxiosPromise<any> => {
return request({
url: '/project/projectFile/json/' + id,
method: 'get'
});
};
/**
* 查询项目详细
* @param id
@ -51,6 +64,18 @@ export const updateProject = (data: ProjectForm) => {
});
};
/**
* 上传dxf文件
* @param data
*/
export const upLoadProjectDXF = (data: any) => {
return request({
url: '/project/projectFile/upload/dxf',
method: 'post',
data: data
});
};
/**
* 删除项目
* @param id

View File

@ -2,7 +2,7 @@ export interface ProjectVO {
/**
* id
*/
id: string | number;
id: string;
/**
* 项目名称
@ -13,7 +13,7 @@ export interface ProjectVO {
* 项目简称
*/
shortName: string;
designId: string;
/**
* 父项目id
*/

View File

@ -1,5 +1,18 @@
<template>
<div class="ol-map" id="olMap"></div>
<div class="flex justify-between">
<el-tree
style="width: 340px; height: 500px; overflow-y: auto"
show-checkbox
:data="jsonData"
@check-change="handleCheckChange"
@node-contextmenu="nodeMenu"
>
<template #default="{ node, data }">
<span @dblclick="handlePosition(data)">{{ data.name }}</span>
</template>
</el-tree>
<div class="ol-map" id="olMap"></div>
</div>
</template>
<script lang="ts" setup>
@ -9,17 +22,69 @@ import { Tile as TileLayer } from 'ol/layer'; // OpenLayers的瓦片图层类
import { XYZ, OSM } from 'ol/source'; // OpenLayers的瓦片数据源包括XYZ格式和OpenStreetMap专用的数据源
import { fromLonLat, toLonLat } from 'ol/proj'; // OpenLayers的投影转换函数用于经纬度坐标和投影坐标之间的转换
import { defaults as defaultInteractions, DragRotateAndZoom } from 'ol/interaction'; // OpenLayers的交互类包括默认的交互集合和特定的旋转缩放交互
import { defaults, FullScreen, MousePosition, ScaleLine } from 'ol/control'; // OpenLayers的控件类包括默认的控件集合和特定的全屏、鼠标位置、比例尺控件
import { defaults as defaultControls, defaults, FullScreen, MousePosition, ScaleLine } from 'ol/control'; // OpenLayers的控件类包括默认的控件集合和特定的全屏、鼠标位置、比例尺控件
import Feature from 'ol/Feature'; // OpenLayers的要素类表示地图上的一个对象或实体
import Point from 'ol/geom/Point'; // OpenLayers的点几何类用于表示点状的地理数据
import { Vector as VectorLayer } from 'ol/layer'; // OpenLayers的矢量图层类用于显示矢量数据
import { Vector as VectorSource } from 'ol/source'; // OpenLayers的矢量数据源类用于管理和提供矢量数据
import { Circle as CircleStyle, Style, Stroke, Fill, Icon } from 'ol/style'; // OpenLayers的样式类用于定义图层的样式包括圆形样式、基本样式、边框、填充和图标
import { Circle, Style, Stroke, Fill, Icon } from 'ol/style'; // OpenLayers的样式类用于定义图层的样式包括圆形样式、基本样式、边框、填充和图标
import LineString from 'ol/geom/LineString'; // OpenLayers的线几何类用于表示线状的地理数据
import tb from '@/assets/image/hyfw.png'; // 导入一个图片资源
import Polygon from 'ol/geom/Polygon'; // OpenLayers的多边形几何类用于表示面状的地理数据
import * as turf from '@turf/turf';
import { Snowflake } from '@/utils/snowflake';
const props = defineProps({
treeData: {
type: Array,
default: () => []
},
projectId: String
});
const snowflake = new Snowflake(1, 1);
let map: any = null;
const layerData = reactive<any>({});
const centerPosition = ref(fromLonLat([107.12932603888963, 23.80590052110889]));
const nodeMenu = (e: any) => {
console.log(e);
};
const jsonData = computed(() => {
props.treeData.forEach((item: any) => {
item.features.forEach((itm, idx) => {
itm.geometry.id = snowflake.nextId();
itm.geometry.coordinates = convertStrToNum(itm.geometry.coordinates);
});
});
return props.treeData;
});
console.log(jsonData);
const handlePosition = (data: any) => {
//切换中心点
centerPosition.value = fromLonLat(turf.center(data).geometry.coordinates);
console.log(turf.center(data));
map.getView().setCenter(centerPosition.value);
};
const handleCheckChange = (data: any, bool) => {
if (bool) {
if (!layerData[data.features[0].properties.id]) {
data.features.forEach((item: any) => {
creatPoint(item.geometry.coordinates, item.geometry.type, item.geometry.id);
});
} else {
data.features.forEach((item: any) => {
map.addLayer(layerData[item.geometry.id]);
});
}
} else {
data.features.forEach((item, index) => {
map.removeLayer(layerData[item.geometry.id]);
});
}
// creatPoint(fromLonLat(data.geometry.coordinates), data.geometry.type);
};
function initOLMap() {
// 创造地图实例
map = new Map({
@ -32,7 +97,7 @@ function initOLMap() {
new TileLayer({
// 设置图层的数据源为XYZ类型。XYZ是一个通用的瓦片图层源它允许你通过URL模板来获取瓦片
source: new XYZ({
url: 'http://t4.tianditu.com/DataServer?T=img_c&tk=ebc08347687dacd6c8feed51ffb4f9fc&x={x}&y={y}&l={z}'
url: 'https://webrd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}'
})
})
],
@ -40,31 +105,42 @@ function initOLMap() {
// View表示地图的视图它定义了地图的中心点、缩放级别、旋转角度等参数。
view: new View({
// fromLonLat是一个函数用于将经纬度坐标转换为地图的坐标系统。
center: fromLonLat([104.924652, 76.208527]), //地图中心点
center: centerPosition.value, //地图中心点
zoom: 15, // 缩放级别
minZoom: 0, // 最小缩放级别
maxZoom: 18, // 最大缩放级别
// maxZoom: 18, // 最大缩放级别
constrainResolution: true // 因为存在非整数的缩放级别所以设置该参数为true来让每次缩放结束后自动缩放到距离最近的一个整数级别这个必须要设置当缩放在非整数级别时地图会糊
// projection: 'EPSG:4326' // 投影坐标系默认是3857
}),
// 按住shift进行旋转
interactions: defaultInteractions().extend([new DragRotateAndZoom()]),
// interactions: defaultInteractions().extend([new DragRotateAndZoom()]),
// 控件
controls: defaults().extend([
// new FullScreen(), // 全屏
// new MousePosition(), // 显示鼠标当前位置的地图坐标
// new ScaleLine() // 显示比例尺
// controls: defaults().extend([
// new FullScreen(), // 全屏
// new MousePosition(), // 显示鼠标当前位置的地图坐标
// new ScaleLine() // 显示比例尺
// ])
//加载控件到地图容器中
controls: defaultControls({
zoom: false,
rotate: false,
attribution: false
}).extend([
new FullScreen() // 全屏
])
});
// 事件
map.on('moveend', (e: any) => {
console.log('地图移动', e);
// console.log('地图移动', e);
// 获取当前缩放级别
var zoomLevel = map.getView().getZoom();
console.log('当前缩放级别:', zoomLevel);
// console.log('当前缩放级别:', zoomLevel);
});
map.on('rendercomplete', () => {
console.log('渲染完成');
// console.log('渲染完成');
});
map.on('click', (e: any) => {
var coordinate = e.coordinate;
@ -76,66 +152,102 @@ function initOLMap() {
});
}
//递归字符串数组变成数字
function convertStrToNum(arr) {
if (typeof arr === 'string') {
const num = Number(arr);
return isNaN(num) ? arr : num;
} else if (Array.isArray(arr)) {
return arr.map((item) => convertStrToNum(item));
}
return arr;
}
/**
* Date:2024/3/26
* Author:zx
* Function:【面】
* @param 无
*/
const faceMethod = () => {
// 定义多边形的坐标数组,这里使用一个简单的正方形作为示例
let coordinates = [
[
fromLonLat([104.92463054232786, 76.20886348492309]),
fromLonLat([104.93329367029872, 76.20920235946437]),
fromLonLat([104.93078312266078, 76.20792354487821]),
fromLonLat([104.92466768610683, 76.20791331389265]),
fromLonLat([104.92463054232786, 76.20886348492309])
]
];
const creatPoint = (pointObj: Array<any>, type: string, id: string) => {
// 创建多边形的几何对象
let polygon = new Polygon(coordinates);
let polygon;
if (type === 'Point') {
polygon = new Point(fromLonLat(pointObj));
} else if (type === 'LineString') {
const lineStringData = pointObj.map((arr: any) => fromLonLat(arr));
polygon = new Polygon([lineStringData]);
} else {
const polygonData = pointObj.map((arr: any) => arr.map((i: any) => fromLonLat(i)));
polygon = new Polygon(polygonData);
}
// 创建特征Feature
let polygonFeature = new Feature({
geometry: polygon
});
// 设置多边形的样式Style
polygonFeature.setStyle(
new Style({
stroke: new Stroke({
color: 'red', // 多边形边界线的颜色
width: 2 // 多边形边界线的宽度
}),
const pointStyle = new Style({
image: new Circle({
radius: 2,
fill: new Fill({
color: 'rgba(255, 0, 0, 0.1)' // 多边形填充颜色,这里设置为半透明红色
color: 'red'
})
})
);
});
const polygonStyle = new Style({
stroke: new Stroke({
color: type === 'LineString' ? 'skyblue' : 'purple', // 多边形边界线的颜色
width: 2 // 多边形边界线的宽度
}),
fill: new Fill({
color: 'transparent' // 多边形填充颜色,这里设置为半透明红色
})
});
// 设置多边形的样式Style
polygonFeature.setStyle(type === 'Point' ? pointStyle : polygonStyle);
// 创建和添加特征到源Source
let source = new VectorSource();
source.addFeature(polygonFeature);
// 创建图层并设置源Layer
let layer = new VectorLayer();
layer.setSource(source);
layerData[id] = new VectorLayer();
layerData[id].setSource(source);
// 将图层添加到地图上
map.addLayer(layer);
map.addLayer(layerData[id]);
};
onMounted(() => {
// 地图初始化
initOLMap();
faceMethod();
});
onDeactivated(() => {
console.log('地图销毁');
map.getOverlays().clear();
});
</script>
<style scoped>
<style scoped lang="scss">
.ol-map {
height: 450px;
width: 1000px;
width: 950px;
margin: 0 auto;
}
.ol-custome-full-screen {
border-radius: 3px;
font-size: 12px;
padding: 5px 11px;
height: 24px;
background-color: #409eff;
color: #fff;
border: 1px solid #409eff;
&:active {
background-color: #337ecc;
border-color: #66b1ff;
}
&:hover {
background-color: #79bbff;
border-color: #79bbff;
}
}
</style>

71
src/utils/snowflake.ts Normal file
View File

@ -0,0 +1,71 @@
export class Snowflake {
private startTimeStamp: number = 1609459200000;
private workerIdBits: number = 5;
private dataCenterIdBits: number = 5;
private sequenceBits: number = 12;
private maxWorkerId: number = -1 ^ (-1 << this.workerIdBits);
private maxDataCenterId: number = -1 ^ (-1 << this.dataCenterIdBits);
private workerIdShift: number = this.sequenceBits;
private dataCenterIdShift: number = this.sequenceBits + this.workerIdBits;
private timestampLeftShift: number = this.sequenceBits + this.workerIdBits + this.dataCenterIdBits;
private sequenceMask: number = -1 ^ (-1 << this.sequenceBits);
private workerId: number;
private dataCenterId: number;
private sequence: number = 0;
private lastTimestamp: number = -1;
constructor(workerId: number, dataCenterId: number) {
if (workerId > this.maxWorkerId || workerId < 0) {
throw new Error(`Worker ID 不能大于 ${this.maxWorkerId} 或小于 0`);
}
if (dataCenterId > this.maxDataCenterId || dataCenterId < 0) {
throw new Error(`数据中心 ID 不能大于 ${this.maxDataCenterId} 或小于 0`);
}
this.workerId = workerId;
this.dataCenterId = dataCenterId;
}
public nextId(): number {
let timestamp = this.getCurrentTimestamp();
if (timestamp < this.lastTimestamp) {
throw new Error('检测到时钟回拨,拒绝生成 ID');
}
if (timestamp === this.lastTimestamp) {
this.sequence = (this.sequence + 1) & this.sequenceMask;
if (this.sequence === 0) {
timestamp = this.waitNextMillis(this.lastTimestamp);
}
} else {
this.sequence = 0;
}
this.lastTimestamp = timestamp;
return (
((timestamp - this.startTimeStamp) << this.timestampLeftShift) |
(this.dataCenterId << this.dataCenterIdShift) |
(this.workerId << this.workerIdShift) |
this.sequence
);
}
private getCurrentTimestamp(): number {
return Date.now();
}
private waitNextMillis(lastTimestamp: number): number {
let timestamp = this.getCurrentTimestamp();
while (timestamp <= lastTimestamp) {
timestamp = this.getCurrentTimestamp();
}
return timestamp;
}
}
// 使用示例
// const workerId: number = 1;
// const dataCenterId: number = 1;
// const snowflake = new Snowflake(workerId, dataCenterId);
// const id: number = snowflake.nextId();
// console.log('生成的唯一 ID:', id);

View File

@ -16,7 +16,6 @@
<p>{{ visitCount }}</p>
</div>
</div>
<openLayersMap></openLayersMap>
</div>
</template>

View File

@ -69,12 +69,34 @@
<el-table-column label="开工时间" align="center" prop="onStreamTime" width="120" />
<el-table-column label="打卡范围" align="center" prop="punchRange" />
<el-table-column label="设计总量" align="center" prop="designTotal" />
<el-table-column label="是否上传DXF" align="center" prop="designId" width="140">
<template #default="scope">
<el-link
:type="scope.row.designId ? 'primary' : 'default'"
:disabled="!scope.row.designId"
@click="handleOpenLayer(scope.row)"
v-loading.fullscreen.lock="fullscreenLoading"
>{{ scope.row.designId ? '已上传' : '未上传' }}</el-link
>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column fixed="right" label="操作" align="center" class-name="small-padding fixed-width" width="260">
<el-table-column fixed="right" label="操作" align="center" class-name="small-padding fixed-width" width="400">
<template #default="scope">
<el-space>
<el-button link type="primary" icon="FolderOpened" @click="handleShowUpload(scope.row)">导入安全协议书 </el-button>
<file-upload
:limit="1"
:fileSize="200"
:fileType="['dxf']"
v-model:model-value="dxfFile"
uploadUrl="/project/projectFile/upload/dxf"
:data="{ projectId: scope.row.id }"
>
<el-button link type="primary" icon="upload">上传DXF </el-button>
</file-upload>
<el-button link type="success" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['project:project:edit']">修改 </el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['project:project:remove']">删除 </el-button>
</el-space>
@ -232,20 +254,26 @@
</div>
</template>
</el-dialog>
<!-- //选取项目地址弹窗 -->
<el-dialog v-model="amapStatus" :title="form.projectName + '-获取经纬度'" width="80%">
<amap height="620px" @setLocation="setPoi"></amap>
<!-- <template #footer>
<div class="dialog-footer">
<el-button v-loading="buttonLoading" type="primary" @click="amapStatus = false"> 确定</el-button>
<el-button @click="amapStatus = false">取消</el-button>
</div>
</template> -->
</el-dialog>
<!-- 选取方阵地址 -->
<el-dialog title="设置方阵" v-model="polygonStatus" width="1400px">
<open-layers-map :tree-data="jsonData" :project-id="projectId"></open-layers-map>
<template #footer>
<span>
<el-button @click="polygonStatus = false">取消</el-button>
<el-button type="primary" @click="">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup name="Project" lang="ts">
import { addProject, delProject, getProject, listProject, updateProject } from '@/api/project/project';
import { addProject, delProject, getProject, listDXFProject, listProject, updateProject } from '@/api/project/project';
import { ProjectForm, ProjectQuery, ProjectVO, locationType } from '@/api/project/project/types';
import amap from '@/components/amap/index.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@ -266,10 +294,15 @@ const total = ref(0);
const amapStatus = ref(false);
const queryFormRef = ref<ElFormInstance>();
const projectFormRef = ref<ElFormInstance>();
const polygonStatus = ref(false);
const dxfFile = ref(null);
const projectId = ref<string>('');
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const jsonData = ref(null);
const fullscreenLoading = ref(false);
const initFormData: ProjectForm = {
id: undefined,
@ -426,6 +459,15 @@ const handleShowUpload = (row?: ProjectVO) => {
uploadVisible.value = true;
};
const handleOpenLayer = async (row: ProjectVO) => {
fullscreenLoading.value = true;
const res = await listDXFProject(row.designId);
projectId.value = row.id;
jsonData.value = res.data.layers;
polygonStatus.value = true;
fullscreenLoading.value = false;
};
const updateProjectFile = async () => {
buttonLoading.value = true;
await updateProject(fileUploadParam.value);