@@ -1,7 +1,7 @@ | |||
<template> | |||
<div class="overlay_container"> | |||
<div class="overlay_head"> | |||
<img class="close_img" src="@/assets/point/close.png" alt="" @click="close"> | |||
<img class="close_img" src="@/assets/point/close.png" alt="" @click="updateVisible"> | |||
</div> | |||
<div class="overlay_main"> | |||
<n-form | |||
@@ -29,10 +29,14 @@ | |||
</n-form-item> | |||
</n-form> | |||
</div> | |||
<div class="btn_list"> | |||
<n-button class="btn_item" type="primary" @click="makeSure">确定</n-button> | |||
<n-button class="btn_item" @click="pass">忽略</n-button> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { toRefs, reactive } from 'vue' | |||
import { toRefs, reactive, watch } from 'vue' | |||
export default { | |||
name: 'OverLay', | |||
props: { | |||
@@ -41,7 +45,11 @@ export default { | |||
default: () => null | |||
} | |||
}, | |||
emits: { 'close': null }, | |||
emits: { | |||
'close': null, | |||
'confirm': null, | |||
'ignore': null | |||
}, | |||
setup(props, { emit }) { | |||
const data = reactive({ | |||
form: props.data, | |||
@@ -49,13 +57,25 @@ export default { | |||
{ value: 0, label: '林班' } | |||
] | |||
}) | |||
function close() { | |||
watch(() => props.data, () => { | |||
data.form = props.data | |||
}) | |||
const updateVisible = function() { | |||
emit('close') | |||
} | |||
function makeSure() { | |||
emit('confirm') | |||
updateVisible() | |||
} | |||
function pass() { | |||
emit('ignore') | |||
updateVisible() | |||
} | |||
return { | |||
...toRefs(data), | |||
close | |||
updateVisible, | |||
makeSure, | |||
pass | |||
} | |||
} | |||
@@ -65,9 +85,11 @@ export default { | |||
.overlay_container { | |||
width: 100%; | |||
height: 436px; | |||
border-radius: 6px; | |||
padding: 0; | |||
overflow-y: scroll; | |||
background-color: #fff; | |||
position: relative; | |||
} | |||
.overlay_head { | |||
@@ -75,6 +97,9 @@ export default { | |||
display: flex; | |||
justify-content: flex-end; | |||
align-items: center; | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
} | |||
.close_img { | |||
width: 40px; | |||
@@ -84,7 +109,22 @@ export default { | |||
.overlay_main { | |||
width: 100%; | |||
margin-top: 10px; | |||
padding: 0 30px 0 40px; | |||
padding: 40px 30px 60px 40px; | |||
} | |||
.btn_list { | |||
width: 100%; | |||
padding-right: 32px; | |||
position: fixed; | |||
bottom: 20px; | |||
right: 0; | |||
display: flex; | |||
justify-content: flex-end; | |||
align-items: center; | |||
} | |||
.btn_item { | |||
width: 64px; | |||
height: 32px; | |||
margin-left: 20px; | |||
} | |||
</style> |
@@ -2,87 +2,215 @@ | |||
<div class="main_container"> | |||
<div id="track" ref="map" /> | |||
<div id="pointOverlay" class="point_overlay"> | |||
<over-lay :data="problemData" @close="closeOverlay" /> | |||
<over-lay :data="problemData" @close="closeOverlay" @confirm="confirmProblem" @ignore="ignoreProblem" /> | |||
</div> | |||
<ul class="legend_list"> | |||
<li v-for="(item, index) in legendList" :key="index" class="legend_item"> | |||
<div class="legend_point" :style="{background: item.color}" /> | |||
<span class="legend_name">{{ item.name }}</span> | |||
</li> | |||
</ul> | |||
</div> | |||
</template> | |||
<script> | |||
import { reactive, toRefs, onMounted, watch } from 'vue' | |||
import OverLay from './OverLay.vue' | |||
import { initMap, setOverlay } from './util.js' | |||
import { reactive, toRefs, onMounted, watch, ref } from 'vue' | |||
import { styleList, legendList } from './util.js' | |||
import { Map, View, Feature, Overlay } from 'ol' | |||
import 'ol/ol.css' | |||
import { Tile, Vector as VectorLayer } from 'ol/layer' | |||
import LineString from 'ol/geom/LineString' | |||
import { XYZ, Vector as VectorSource } from 'ol/source' | |||
import { transform, fromLonLat } from 'ol/proj' | |||
import * as control from 'ol/control' | |||
import { Select } from 'ol/interaction' | |||
import Point from 'ol/geom/Point' | |||
import OverLay from './OverLay.vue' | |||
export default { | |||
name: 'PositionMsg', | |||
components: { OverLay }, | |||
props: {}, | |||
props: { | |||
data: { | |||
type: Array, | |||
default: () => {} | |||
} | |||
}, | |||
emits: {}, | |||
setup(props, { emit }) { | |||
const data = reactive({ | |||
mapdata: null, // 地图 | |||
view: null, // 视图 | |||
source: null, | |||
layer: null, // 底图 | |||
problemLayer: null, // 问题矢量图 | |||
select: null, | |||
TDimageMap: null, // 影像底图 | |||
problemList: [ | |||
{ content: '河道内存在水生植被', | |||
fileImage: 'https://image.t-aaron.com/XJRW20220725103839/2022-07-25-10-41-46_frame-1500-1680_type-水生植被_VMgRwh05s4clHXCu_s-live-XJRW20220725103839-b73c470768f74422b287981fc75c31c3_AI.jpg', | |||
handlerImage: 'https://image.t-aaron.com/imagedir/kw6vv4m1yw_1658717157035.png', | |||
handlerResult: '', | |||
handlerTime: '2022-07-25 10:45:56', | |||
latitude: '31.829037194418085', | |||
location: '江苏省南京市江宁区秣陵街道东吉大道', | |||
longitude: '118.770222690945', | |||
name: 1, | |||
status: 1, | |||
type: 1, | |||
userName: '运管单位' } | |||
], // 问题矢量点列表 | |||
overlay: null, // overlay弹出框 | |||
problemData: {} // 问题点数据 | |||
problemList: props.data, // 问题矢量点列表 | |||
problemData: {}, // 问题点数据 | |||
legendList: legendList | |||
}) | |||
onMounted(() => { | |||
initMap('track', data.problemList, 'pointOverlay').then(({ mapdata, select, view, overlay, problemLayer }) => { | |||
data.mapdata = mapdata | |||
data.select = select | |||
data.view = view | |||
data.overlay = overlay | |||
data.problemLayer = problemLayer | |||
const mapdata = ref(null) | |||
const select = ref(null) | |||
const problemLayer = ref(null) | |||
const overlay = ref(null) | |||
const view = ref(null) | |||
/** | |||
* 初始化底图 | |||
*/ | |||
const initMap = function() { | |||
const layers = [ | |||
new Tile({ | |||
visible: true, | |||
source: new XYZ({ | |||
url: 'https://tianditu.t-aaron.com/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=f82bb2fd8115c7fb576a8b2fcf738523' | |||
}) | |||
}) | |||
] | |||
view.value = new View({ | |||
maxZoom: 18, | |||
zoom: 4, | |||
center: transform([108.94043, 31.008173], 'EPSG:4326', 'EPSG:3857') | |||
}) | |||
mapdata.value = new Map({ | |||
layers: layers, | |||
target: 'track', | |||
view: view.value, | |||
controls: control.defaults({ | |||
attribution: false, | |||
rotate: false, | |||
zoom: false | |||
}) | |||
}) | |||
mapdata.value.render() | |||
// 添加select并设置选中样式 | |||
select.value = new Select({ | |||
style: (feature) => { | |||
if (feature.getGeometry() instanceof Point) { | |||
return styleList[3] | |||
} | |||
} | |||
}) | |||
mapdata.value.addInteraction(select.value) | |||
// 新建overlay弹出框 | |||
overlay.value = new Overlay({ | |||
element: document.getElementById('pointOverlay'), | |||
autoPan: true | |||
}) | |||
} | |||
onMounted(() => { | |||
initMap() | |||
getLayer(props.data, view.value, mapdata.value) | |||
}) | |||
watch( | |||
() => data.select, | |||
(value) => { | |||
data.select.on('select', (e) => { | |||
if (e.selected.length > 0) { | |||
const properties = e.selected[0].getProperties() | |||
const geom = properties.geometry | |||
if (geom instanceof Point) { | |||
data.problemData = properties.item | |||
const coordinate = e.mapBrowserEvent.coordinate | |||
data.overlay.setPosition(coordinate) | |||
data.overlay.setOffset([30, -400]) | |||
data.mapdata.addOverlay(data.overlay) | |||
} else { | |||
data.mapdata.removeOverlay(data.overlay) | |||
} | |||
/** | |||
* 加载矢量图层 | |||
*/ | |||
const getLayer = function(data) { | |||
const pointFeatures = [] | |||
const points = [] | |||
if (data.length) { | |||
data.forEach((item) => { | |||
const pointItem = [] | |||
pointItem.push(parseFloat(item.longitude)) | |||
pointItem.push(parseFloat(item.latitude)) | |||
const featureItem = new Feature(new Point(fromLonLat(pointItem))) | |||
points.push(fromLonLat(pointItem)) | |||
featureItem.setProperties({ item: item, type: 'problem' }, false) | |||
featureItem.setStyle(styleList[item.status]) | |||
pointFeatures.push(featureItem) | |||
}) | |||
const pointSource = new VectorSource({ | |||
wrapX: false, | |||
features: pointFeatures | |||
}) | |||
problemLayer.value = new VectorLayer({ | |||
source: pointSource, | |||
zIndex: 12 | |||
}) | |||
let geom | |||
if (points.length > 1) { | |||
geom = new LineString(points) | |||
} else if (points.length > 0) { | |||
geom = new Point(points[0]) | |||
} | |||
if (geom) { | |||
view.value.fit(geom, { padding: [150, 200, 150, 200] }) | |||
} | |||
mapdata.value.addLayer(problemLayer.value) | |||
} | |||
} | |||
/** | |||
* 删除矢量图层 | |||
*/ | |||
const removeVector = function(layer) { | |||
layer.getSource().clear() | |||
mapdata.value.removeLayer(layer) | |||
problemLayer.value = null | |||
} | |||
/** | |||
* 选择矢量点数据 | |||
*/ | |||
const setOverlay = function() { | |||
select.value.on('select', (e) => { | |||
if (e.selected.length > 0) { | |||
const properties = e.selected[0].getProperties() | |||
const geom = properties.geometry | |||
if (geom instanceof Point) { | |||
data.problemData = properties.item | |||
const coordinate = e.mapBrowserEvent.coordinate | |||
overlay.value.setPosition(coordinate) | |||
overlay.value.setOffset([30, -400]) | |||
mapdata.value.addOverlay(overlay.value) | |||
} else { | |||
data.mapdata.removeOverlay(data.overlay) | |||
mapdata.value.removeOverlay(overlay.value) | |||
} | |||
}) | |||
} else { | |||
mapdata.value.removeOverlay(overlay.value) | |||
} | |||
}) | |||
} | |||
watch( | |||
() => select.value, | |||
(value) => { | |||
setOverlay(mapdata.value, overlay.value) | |||
} | |||
) | |||
watch(() => props.data, (value) => { | |||
if (value) { | |||
if (mapdata.value) { | |||
removeVector(problemLayer.value) | |||
getLayer(value) | |||
} | |||
} | |||
}) | |||
/** | |||
* 关闭overlay | |||
*/ | |||
const closeOverlay = () => { | |||
data.select.getFeatures().clear() | |||
data.mapdata.removeOverlay(data.overlay) | |||
select.value.getFeatures().clear() | |||
mapdata.value.removeOverlay(overlay.value) | |||
} | |||
/** | |||
* 忽略 | |||
*/ | |||
const ignoreProblem = function() { | |||
} | |||
/** | |||
* 确认 | |||
*/ | |||
const confirmProblem = function() { | |||
} | |||
return { | |||
...toRefs(data), | |||
closeOverlay | |||
removeVector, | |||
closeOverlay, | |||
confirmProblem, | |||
ignoreProblem | |||
} | |||
} | |||
} | |||
@@ -101,4 +229,34 @@ export default { | |||
.point_overlay { | |||
width: 400px; | |||
} | |||
.legend_list { | |||
width: 172px; | |||
height: 173px; | |||
padding: 10px 20px; | |||
background: rgba(31, 31, 31, 0.5); | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: space-between; | |||
align-items: flex-start; | |||
position: absolute; | |||
bottom: 0; | |||
right: 0; | |||
} | |||
.legend_item { | |||
width: 100%; | |||
display: flex; | |||
justify-content: flex-start; | |||
align-items: center; | |||
} | |||
.legend_point { | |||
width: 24px; | |||
height: 24px; | |||
margin-right: 24px; | |||
border-radius: 50%; | |||
border: 1px solid #fff; | |||
} | |||
.legend_name { | |||
font-size: 16px; | |||
color: #fff; | |||
} | |||
</style> |
@@ -1,118 +1,16 @@ | |||
import { Map, View, Feature, Overlay } from 'ol' | |||
import 'ol/ol.css' | |||
import Projection from 'ol/proj/Projection' | |||
import { Tile, Vector as VectorLayer } from 'ol/layer' | |||
import LineString from 'ol/geom/LineString' | |||
import { ImageStatic, TileWMS, XYZ, Vector as VectorSource } from 'ol/source' | |||
import { transform, fromLonLat } from 'ol/proj' | |||
import * as control from 'ol/control' | |||
import { Select } from 'ol/interaction' | |||
import { Stroke, Style, Icon } from 'ol/style' | |||
import Point from 'ol/geom/Point' | |||
import imgAll from '../../assets/point/all.png' | |||
import { Style, Icon } from 'ol/style' | |||
import imgAwait from '../../assets/point/await.png' | |||
import imgChecked from '../../assets/point/checked.png' | |||
/** | |||
* 初始化地图 | |||
* @param {*} ids dom的id | |||
* @param {*} mapdata 地图对象 | |||
* @param {*} blanklayerList 显示图层列表 | |||
* @param {*} datalayerList 待添加图层列表 | |||
* @param {*} zoom 层级 | |||
* @param {*} interactions 控制 | |||
*/ | |||
export function initMap(ids, data, overlayId) { | |||
const layers = [ | |||
new Tile({ | |||
visible: true, | |||
source: new XYZ({ | |||
url: 'https://tianditu.t-aaron.com/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=f82bb2fd8115c7fb576a8b2fcf738523' | |||
}) | |||
}) | |||
] | |||
const view = new View({ | |||
maxZoom: 18, | |||
zoom: 4, | |||
center: transform([108.94043, 31.008173], 'EPSG:4326', 'EPSG:3857') | |||
}) | |||
const mapdata = new Map({ | |||
layers: layers, | |||
target: ids, | |||
view: view, | |||
controls: control.defaults({ | |||
attribution: false, | |||
rotate: false, | |||
zoom: false | |||
}) | |||
}) | |||
mapdata.render() | |||
const problemLayer = null | |||
getLayers(data, problemLayer, view, mapdata) | |||
const select = new Select({ | |||
style: (feature) => { | |||
const properties = feature.getProperties() | |||
if (feature.getGeometry() instanceof Point) { | |||
return styleList[properties.item.status] | |||
} | |||
} | |||
}) | |||
mapdata.addInteraction(select) | |||
// 新建overlay弹出框 | |||
const overlay = new Overlay({ | |||
element: document.getElementById(overlayId), | |||
autoPan: true | |||
}) | |||
return Promise.resolve({ | |||
mapdata, | |||
view, | |||
select, | |||
overlay | |||
}) | |||
} | |||
/** | |||
* 选择矢量图层并添加overlay | |||
* @param {*} overlay | |||
* @param {*} select 选择 | |||
* @param {*} mapdata 地图 | |||
* @param {*} problemData 问题数据 | |||
*/ | |||
export function setOverlay(overlay, select, mapdata, problemData) { | |||
console.log(select) | |||
select.on('select', (e) => { | |||
if (e.selected.length > 0) { | |||
const properties = e.selected[0].getProperties() | |||
const geom = properties.geometry | |||
if (geom instanceof Point) { | |||
problemData = properties.item | |||
const coordinate = e.mapBrowserEvent.coordinate | |||
overlay.setPosition(coordinate) | |||
overlay.setOffset([30, -400]) | |||
mapdata.addOverlay(overlay) | |||
} else { | |||
mapdata.removeOverlay(overlay) | |||
} | |||
} else { | |||
mapdata.removeOverlay(overlay) | |||
} | |||
}) | |||
return Promise.resolve({ | |||
overlay, | |||
select, | |||
mapdata, | |||
problemData | |||
}) | |||
} | |||
import imgIgnored from '../../assets/point/ignored.png' | |||
import imgSelected from '../../assets/point/selected.png' | |||
// 矢量图标样式 | |||
const styleList = [ | |||
export const styleList = [ | |||
new Style({ | |||
image: new Icon({ | |||
anchor: [0.5, 0.5], | |||
src: imgAll, | |||
src: imgAwait, | |||
crossOrigin: '', | |||
scale: [1, 1], | |||
rotateWithView: true | |||
@@ -126,71 +24,42 @@ const styleList = [ | |||
scale: [1, 1], | |||
rotateWithView: true | |||
}) | |||
}) | |||
] | |||
/** | |||
* 生成矢量图层 | |||
* @param {*} datalayerList 图层数据列表 | |||
* [{paramlayer: '图层名',type: 'wms' 类型}] | |||
* @returns | |||
*/ | |||
function getLayers(data, layer, view, map) { | |||
const pointFeatures = [] | |||
const points = [] | |||
if (data.length) { | |||
data.forEach((item) => { | |||
const pointItem = [] | |||
pointItem.push(parseFloat(item.longitude)) | |||
pointItem.push(parseFloat(item.latitude)) | |||
const featureItem = new Feature(new Point(fromLonLat(pointItem))) | |||
points.push(fromLonLat(pointItem)) | |||
featureItem.setProperties({ item: item, type: 'problem' }, false) | |||
featureItem.setStyle(styleList[item.status]) | |||
pointFeatures.push(featureItem) | |||
}) | |||
const pointSource = new VectorSource({ | |||
wrapX: false, | |||
features: pointFeatures | |||
}), | |||
new Style({ | |||
image: new Icon({ | |||
anchor: [0.5, 0.5], | |||
src: imgIgnored, | |||
crossOrigin: '', | |||
scale: [1, 1], | |||
rotateWithView: true | |||
}) | |||
layer = new VectorLayer({ | |||
source: pointSource, | |||
zIndex: 12 | |||
}), | |||
new Style({ | |||
image: new Icon({ | |||
anchor: [0.5, 0.5], | |||
src: imgSelected, | |||
crossOrigin: '', | |||
scale: [1, 1], | |||
rotateWithView: true | |||
}) | |||
let geom | |||
if (points.length > 1) { | |||
geom = new LineString(points) | |||
} else if (points.length > 0) { | |||
geom = new Point(points[0]) | |||
} | |||
if (geom) { | |||
view.fit(geom, { padding: [100, 100, 100, 100] }) | |||
} | |||
map.addLayer(layer) | |||
} | |||
} | |||
/** | |||
* 引入静态图标创建图标对象 | |||
**/ | |||
const imgstyle = new Style({ | |||
image: new Icon({ | |||
anchor: [0.5, 1], | |||
src: import.meta.env.BASE_URL + `img/icon-biaozhu.png`, | |||
scale: 0.5 | |||
}) | |||
}) | |||
/** | |||
* 使用feature | |||
* coordinate 坐标 | |||
**/ | |||
export function setTrmFeature(coordinate) { | |||
const feature = new Feature({ | |||
type: 'icon', | |||
geometry: new Point(coordinate) | |||
}) | |||
feature.setStyle(imgstyle) | |||
return feature | |||
} | |||
] | |||
export const legendList = [ | |||
{ | |||
name: '待确认问题', | |||
color: '#F1EA0B' | |||
}, | |||
{ | |||
name: '当前问题', | |||
color: '#1AFA29' | |||
}, | |||
{ | |||
name: '已忽略问题', | |||
color: '#FF8A15' | |||
}, | |||
{ | |||
name: '已确定问题', | |||
color: '#FF3333' | |||
} | |||
] |
@@ -33,7 +33,7 @@ export default [ | |||
children: [ | |||
{ | |||
path: 'user', | |||
component: () => import('@/views/system/user/index.vue'), | |||
component: () => import('@/views/system-manage/user-manage/index.vue'), | |||
name: 'SystemUser', | |||
title: '用户管理', | |||
meta: { | |||
@@ -42,7 +42,7 @@ export default [ | |||
}, | |||
{ | |||
path: 'role', | |||
component: () => import('@/views/system/role/index.vue'), | |||
component: () => import('@/views/system-manage/role-manage/index.vue'), | |||
name: 'SystemRole', | |||
title: '角色管理', | |||
meta: { | |||
@@ -51,7 +51,7 @@ export default [ | |||
}, | |||
{ | |||
path: 'dept', | |||
component: () => import('@/views/system/dept/index.vue'), | |||
component: () => import('@/views/system-manage/department-manage/index.vue'), | |||
name: 'SystemDept', | |||
title: '部门管理', | |||
meta: { | |||
@@ -60,7 +60,7 @@ export default [ | |||
}, | |||
{ | |||
path: 'menu', | |||
component: () => import('@/views/system/menu/index.vue'), | |||
component: () => import('@/views/system-manage/menu-manage/index.vue'), | |||
name: 'SystemMenu', | |||
title: '菜单管理', | |||
meta: { |
@@ -2,7 +2,7 @@ | |||
<n-drawer v-bind="getDrawerOptions" @update:show="handleDrawerColse"> | |||
<n-drawer-content closable title="疑似问题核实"> | |||
<!-- <QuestionPage /> --> | |||
<position-msg /> | |||
<position-msg :data="problemList" /> | |||
</n-drawer-content> | |||
</n-drawer> | |||
</template> | |||
@@ -31,7 +31,34 @@ export default defineComponent({ | |||
'update:visible': null | |||
}, | |||
setup(props, { emit }) { | |||
const data = reactive({}) | |||
const data = reactive({ | |||
problemList: [ | |||
{ content: '河道内存在水生植被', | |||
fileImage: 'https://image.t-aaron.com/XJRW20220725103839/2022-07-25-10-41-46_frame-1500-1680_type-水生植被_VMgRwh05s4clHXCu_s-live-XJRW20220725103839-b73c470768f74422b287981fc75c31c3_AI.jpg', | |||
handlerImage: 'https://image.t-aaron.com/imagedir/kw6vv4m1yw_1658717157035.png', | |||
handlerResult: '', | |||
handlerTime: '2022-07-25 10:45:56', | |||
latitude: '31.829037194418085', | |||
location: '江苏省南京市江宁区秣陵街道东吉大道', | |||
longitude: '118.770222690945', | |||
name: 1, | |||
status: 1, | |||
type: 1, | |||
userName: '运管单位' }, | |||
{ content: '水生植被', | |||
fileImage: 'https://image.t-aaron.com/XJRW20220725103839/2022-07-25-10-41-46_frame-1500-1680_type-水生植被_VMgRwh05s4clHXCu_s-live-XJRW20220725103839-b73c470768f74422b287981fc75c31c3_AI.jpg', | |||
handlerImage: 'https://image.t-aaron.com/imagedir/kw6vv4m1yw_1658717157035.png', | |||
handlerResult: '', | |||
handlerTime: '2022-07-25 10:45:56', | |||
latitude: '31.129037194418085', | |||
location: '江苏省南京市江宁区秣陵街道东吉大道', | |||
longitude: '118.880222690945', | |||
name: 1, | |||
status: 0, | |||
type: 2, | |||
userName: '运管单位' } | |||
] | |||
}) | |||
/* 获取抽屉的信息 */ | |||
const getDrawerOptions = computed(() => { |