"axios": "^0.26.1", | "axios": "^0.26.1", | ||||
"dayjs": "^1.11.2", | "dayjs": "^1.11.2", | ||||
"mockjs": "^1.1.0", | "mockjs": "^1.1.0", | ||||
"ol": "^6.15.1", | |||||
"pinia": "^2.0.13", | "pinia": "^2.0.13", | ||||
"pinia-plugin-persist": "^1.0.0", | "pinia-plugin-persist": "^1.0.0", | ||||
"tinymce": "^5.10.2", | "tinymce": "^5.10.2", | ||||
"vite-plugin-svg-icons": "^2.0.1", | |||||
"vue": "^3.2.16", | "vue": "^3.2.16", | ||||
"vue-router": "^4.0.14", | "vue-router": "^4.0.14", | ||||
"vuedraggable": "^4.1.0" | "vuedraggable": "^4.1.0" |
<template> | |||||
<div class="overlay_container"> | |||||
<div class="overlay_head"> | |||||
<img class="close_img" src="@/assets/point/close.png" alt="" @click="updateVisible"> | |||||
</div> | |||||
<div class="overlay_main"> | |||||
<n-form | |||||
ref="formRef" | |||||
:model="form" | |||||
:label-width="80" | |||||
label-placement="left" | |||||
require-mark-placement="left" | |||||
> | |||||
<n-form-item label="问题类型:"> | |||||
<n-select | |||||
v-model:value="form.type" | |||||
:options="typeList" | |||||
placeholder="请选择问题类型" | |||||
/> | |||||
</n-form-item> | |||||
<n-form-item label="问题描述:"> | |||||
<span>{{ form.content }}</span> | |||||
</n-form-item> | |||||
<n-form-item label="经纬度:"> | |||||
<span>{{ form.longitude }},{{ form.latitude }}</span> | |||||
</n-form-item> | |||||
<n-form-item label="问题图片:"> | |||||
<img class="image_size" :src="form.fileImage" alt="" style="width: 108px; height: auto; margin: 0 5px 5px 0"> | |||||
</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, watch, inject } from 'vue' | |||||
export default { | |||||
name: 'OverLay', | |||||
props: { | |||||
data: { | |||||
type: Object, | |||||
default: () => null | |||||
} | |||||
}, | |||||
emits: { | |||||
'close': null | |||||
}, | |||||
setup(props, { emit }) { | |||||
const data = reactive({ | |||||
form: {}, | |||||
typeList: [ | |||||
{ value: 1, label: '林斑' }, | |||||
{ value: 2, label: '病虫害' } | |||||
] | |||||
}) | |||||
/* 引入祖先组件注册的方法 */ | |||||
const confirm = inject('confirm', null) | |||||
const ignore = inject('ignore', null) | |||||
watch(() => props.data, () => { | |||||
data.form = Object.assign({}, props.data) | |||||
}) | |||||
const updateVisible = function() { | |||||
emit('close') | |||||
} | |||||
function makeSure() { | |||||
confirm(data.form) | |||||
updateVisible() | |||||
} | |||||
function pass() { | |||||
ignore(data.form) | |||||
updateVisible() | |||||
} | |||||
return { | |||||
...toRefs(data), | |||||
updateVisible, | |||||
makeSure, | |||||
pass | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="scss" scoped> | |||||
.overlay_container { | |||||
width: 100%; | |||||
height: 436px; | |||||
border-radius: 6px; | |||||
padding: 0; | |||||
overflow-y: scroll; | |||||
background-color: #fff; | |||||
position: relative; | |||||
} | |||||
.overlay_head { | |||||
width: 100%; | |||||
display: flex; | |||||
justify-content: flex-end; | |||||
align-items: center; | |||||
position: fixed; | |||||
top: 0; | |||||
left: 0; | |||||
} | |||||
.close_img { | |||||
width: 40px; | |||||
height: 40px; | |||||
cursor: pointer; | |||||
} | |||||
.overlay_main { | |||||
width: 100%; | |||||
margin-top: 10px; | |||||
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> |
<template> | |||||
<div class="main_container"> | |||||
<div id="track" ref="map" /> | |||||
<div id="pointOverlay" class="point_overlay"> | |||||
<over-lay :data="problemData" @close="closeOverlay" /> | |||||
</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, 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: { | |||||
data: { | |||||
type: Array, | |||||
default: () => {} | |||||
} | |||||
}, | |||||
emits: {}, | |||||
setup(props, { emit }) { | |||||
const data = reactive({ | |||||
problemList: props.data, // 问题矢量点列表 | |||||
problemData: {}, // 问题点数据 | |||||
legendList: legendList | |||||
}) | |||||
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[0] | |||||
} | |||||
} | |||||
}) | |||||
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) | |||||
}) | |||||
/** | |||||
* 加载矢量图层 | |||||
*/ | |||||
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 { | |||||
closeOverlay() | |||||
} | |||||
} else { | |||||
closeOverlay() | |||||
} | |||||
}) | |||||
} | |||||
watch( | |||||
() => select.value, | |||||
(value) => { | |||||
setOverlay(mapdata.value, overlay.value) | |||||
} | |||||
) | |||||
watch(() => props.data, (value) => { | |||||
if (value) { | |||||
if (mapdata.value) { | |||||
removeVector(problemLayer.value) | |||||
getLayer(props.data) | |||||
} | |||||
} | |||||
}) | |||||
/** | |||||
* 关闭overlay | |||||
*/ | |||||
const closeOverlay = () => { | |||||
data.problemData = {} | |||||
select.value.getFeatures().clear() | |||||
mapdata.value.removeOverlay(overlay.value) | |||||
} | |||||
return { | |||||
...toRefs(data), | |||||
removeVector, | |||||
closeOverlay | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped> | |||||
.main_container { | |||||
width: 100%; | |||||
height: 100%; | |||||
position: relative; | |||||
overflow: hidden; | |||||
} | |||||
#track { | |||||
width: 100%; | |||||
height: 100%; | |||||
} | |||||
.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> |
import 'ol/ol.css' | |||||
import { Style, Icon } from 'ol/style' | |||||
import imgAwait from '../../assets/point/await.png' | |||||
import imgChecked from '../../assets/point/checked.png' | |||||
import imgIgnored from '../../assets/point/ignored.png' | |||||
import imgSelected from '../../assets/point/selected.png' | |||||
// 矢量图标样式 | |||||
export const styleList = [ | |||||
new Style({ | |||||
image: new Icon({ | |||||
anchor: [0.5, 0.5], | |||||
src: imgSelected, | |||||
crossOrigin: '', | |||||
scale: [1, 1], | |||||
rotateWithView: true | |||||
}) | |||||
}), | |||||
new Style({ | |||||
image: new Icon({ | |||||
anchor: [0.5, 0.5], | |||||
src: imgAwait, | |||||
crossOrigin: '', | |||||
scale: [1, 1], | |||||
rotateWithView: true | |||||
}) | |||||
}), | |||||
new Style({ | |||||
image: new Icon({ | |||||
anchor: [0.5, 0.5], | |||||
src: imgChecked, | |||||
crossOrigin: '', | |||||
scale: [1, 1], | |||||
rotateWithView: true | |||||
}) | |||||
}), | |||||
new Style({ | |||||
image: new Icon({ | |||||
anchor: [0.5, 0.5], | |||||
src: imgIgnored, | |||||
crossOrigin: '', | |||||
scale: [1, 1], | |||||
rotateWithView: true | |||||
}) | |||||
}) | |||||
] | |||||
export const legendList = [ | |||||
{ | |||||
name: '待确认问题', | |||||
color: '#F1EA0B' | |||||
}, | |||||
{ | |||||
name: '当前问题', | |||||
color: '#1AFA29' | |||||
}, | |||||
{ | |||||
name: '已忽略问题', | |||||
color: '#FF8A15' | |||||
}, | |||||
{ | |||||
name: '已确定问题', | |||||
color: '#FF3333' | |||||
} | |||||
] |
}, | }, | ||||
children: [ | children: [ | ||||
{ | { | ||||
path: 'allTask', | |||||
path: 'all', | |||||
component: () => import('@/views/task-manage/all-task/index.vue'), | component: () => import('@/views/task-manage/all-task/index.vue'), | ||||
name: 'TaskAll', | name: 'TaskAll', | ||||
title: '所有任务', | title: '所有任务', |
'update:visible': null | 'update:visible': null | ||||
}, | }, | ||||
setup(props, { emit }) { | setup(props, { emit }) { | ||||
const data = reactive({}) | |||||
const data = reactive({ | |||||
}) | |||||
/* 获取抽屉的信息 */ | /* 获取抽屉的信息 */ | ||||
const getDrawerOptions = computed(() => { | const getDrawerOptions = computed(() => { |
<DataTable | <DataTable | ||||
ref="tableRef" | ref="tableRef" | ||||
:columns="columns" | :columns="columns" | ||||
:request="loadDataTable" | |||||
:data="data" | |||||
:row-key="(row) => row.id" | :row-key="(row) => row.id" | ||||
size="large" | size="large" | ||||
> | > | ||||
setup() { | setup() { | ||||
const data = reactive({ | const data = reactive({ | ||||
search, | search, | ||||
...toRefs(table) | |||||
...toRefs(table), | |||||
data: [{}] | |||||
}) | }) | ||||
/** | /** |
<template> | |||||
<n-drawer v-bind="getDrawerOptions" @update:show="handleDrawerColse"> | |||||
<n-drawer-content closable title="图片位置"> | |||||
<position-msg :data="problemList" /> | |||||
</n-drawer-content> | |||||
</n-drawer> | |||||
</template> | |||||
<script> | |||||
import { defineComponent, computed, reactive, toRefs, provide } from 'vue' | |||||
import PositionMsg from '@/components/PositionMsg/index.vue' | |||||
export default defineComponent({ | |||||
name: 'PositionDrawer', | |||||
components: { PositionMsg }, | |||||
props: { | |||||
/* 可见 */ | |||||
visible: { | |||||
type: Boolean, | |||||
default: false | |||||
}, | |||||
/* 选中的数据 */ | |||||
data: { | |||||
type: Object, | |||||
default: () => {} | |||||
} | |||||
}, | |||||
emits: { | |||||
'update:visible': null | |||||
}, | |||||
setup(props, { emit }) { | |||||
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: 2, | |||||
type: 2, | |||||
userName: '运管单位' } | |||||
] | |||||
}) | |||||
const confirm = function(params) { | |||||
console.log(params, '确认++++++++++++') | |||||
} | |||||
const ignore = function(params) { | |||||
console.log(params, '忽略------------') | |||||
} | |||||
/* 注册确认方法 */ | |||||
provide('confirm', confirm) | |||||
/* 注册忽略方法 */ | |||||
provide('ignore', ignore) | |||||
/* 获取抽屉的信息 */ | |||||
const getDrawerOptions = computed(() => { | |||||
return { | |||||
show: props.visible, | |||||
width: '100%', | |||||
placement: 'right' | |||||
} | |||||
}) | |||||
/* 关闭抽屉 */ | |||||
function handleDrawerColse() { | |||||
emit('update:visible', false) | |||||
} | |||||
return { | |||||
...toRefs(data), | |||||
getDrawerOptions, | |||||
handleDrawerColse | |||||
} | |||||
} | |||||
}) | |||||
</script> | |||||
<style scoped lang='scss'> | |||||
.n-button+.n-button{ | |||||
margin-left: 30px; | |||||
} | |||||
</style> |
<ConfirmModal v-if="confirmModal" v-model:visible="confirmModal" :data="pageData" @update-data="tableReload" /> | <ConfirmModal v-if="confirmModal" v-model:visible="confirmModal" :data="pageData" @update-data="tableReload" /> | ||||
<!-- 图片位置 --> | |||||
<position-drawer v-model:visible="positionDrawer" :data="rowData" /> | |||||
</template> | </template> | ||||
<script> | <script> | ||||
import HeadSearch from '@/components/Search/index.vue' | import HeadSearch from '@/components/Search/index.vue' | ||||
import DataTable from '@/components/DataTable/index.vue' | import DataTable from '@/components/DataTable/index.vue' | ||||
import ConfirmModal from './components/ConfirmModal.vue' | import ConfirmModal from './components/ConfirmModal.vue' | ||||
import { h, computed, ref, reactive, toRefs } from 'vue' | |||||
import PositionDrawer from './components/PositionDrawer.vue' | |||||
import { ref, reactive, toRefs } from 'vue' | |||||
export default { | export default { | ||||
name: 'QuestionPage', | name: 'QuestionPage', | ||||
components: { HeadSearch, DataTable, ConfirmModal }, | |||||
components: { HeadSearch, DataTable, ConfirmModal, PositionDrawer }, | |||||
setup() { | setup() { | ||||
const data = reactive({ | const data = reactive({ | ||||
search, | search, | ||||
...toRefs(table), | ...toRefs(table), | ||||
data: [ | data: [ | ||||
{ id: 1, image: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg', type: 1 }, | |||||
{ id: 2, image: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel2.jpeg', type: 2 } | |||||
{ id: 1, image: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg', position: '软件园', type: 1 }, | |||||
{ id: 2, image: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel2.jpeg', position: '东吉大道一号', type: 2 } | |||||
], | ], | ||||
pageData: [] | pageData: [] | ||||
}) | }) | ||||
/** | /** |
console.log('问题确认') | console.log('问题确认') | ||||
} | } | ||||
/* 位置 */ | |||||
function handlePositionDrawer(row) { | |||||
data.rowData = row | |||||
data.positionDrawer = true | |||||
} | |||||
function handleImgPreview() { | function handleImgPreview() { | ||||
data.confirmModal = true | data.confirmModal = true | ||||
} | } | ||||
const data = reactive({ | const data = reactive({ | ||||
rowData: {}, | rowData: {}, | ||||
confirmModal: false, | confirmModal: false, | ||||
positionDrawer: false, | |||||
columns: [ | columns: [ | ||||
{ | { | ||||
type: 'selection' | type: 'selection' | ||||
}, | }, | ||||
{ | { | ||||
title: '位置', | title: '位置', | ||||
key: 'name', | |||||
align: 'center' | |||||
key: 'position', | |||||
align: 'center', | |||||
render(row) { | |||||
return h(TableAction, { | |||||
actions: [ | |||||
{ | |||||
label: '图片位置', | |||||
type: 'button', | |||||
props: { | |||||
type: 'primary', | |||||
text: true, | |||||
onClick: handlePositionDrawer.bind(null, row) | |||||
}, | |||||
auth: 'basic_list', | |||||
show: row.status !== 1 | |||||
} | |||||
], | |||||
align: 'center' | |||||
}) | |||||
} | |||||
}, | }, | ||||
{ | { | ||||
title: '备注', | title: '备注', |