Compare commits

...

2 Commits

28 changed files with 189681 additions and 87 deletions

7
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"i18n-ally.localesPaths": [
"src/locales",
"src/locales/lang",
"public/resource/tinymce/langs"
]
}

View File

@ -0,0 +1,6 @@
<template>
<Converge />
</template>
<script lang="ts" setup>
import Converge from '@/views/sys/exception/Converge.vue';
</script>

View File

@ -273,7 +273,7 @@ watchEffect(() => {
const MapboxComponent = ref();
const countyId = ref();
const defaultColor = ref('#FFFFFF');
const layerCenterShow = ref(false);
const layerCenterShow = ref(true);
function getLayerSettings() {
let title = '';
switch (subject.value) {
@ -325,7 +325,8 @@ watchEffect(() => {
break;
}
getConfig({
code: title,
// code: title,
code:"Subject_WFYD",
}).then((res) => {
let obj = JSON.parse(res.codeValue);
layerSettings.value = obj[0];

View File

@ -0,0 +1,318 @@
<template>
<a-spin size="large" tip="图层加载中..." :spinning="spinning" wrapperClassName="solid-spin">
<div class="statistical" id="bg-pan" ref="container">
<Map
style="
position: absolute;
top: 0px;
left: 0px;
height: calc(100vh - 80px);
width: 100%;
z-index: 0;
"
ref="MapboxComponent"
@handlerGetDetails="handlerGetDetails"
@changeLoading="changeLoading"
/>
<div class="legend">
<div class="legend-item" v-for="(item, index) in legends" :key="index">
<div class="legend-dot" :style="{ background: item.color }"></div>
<div class="legend-label">{{ item.label }}</div>
</div>
</div>
<div class="layer-center-container"
:style="style">
<div class="drag-area"
@mousedown="startDrag"></div>
<LayerCenter
@changeLayer="changeLayer"
@changeLabel="changeLabel"
ref="LayerCenterComponent"
></LayerCenter>
</div>
<a-modal
title="详情"
width="100%"
wrap-class-name="full-modal"
v-model:open="showInfoOpen"
:footer="null"
:destroyOnClose="true">
<IdleWatersInsertModal v-if="showInfoType == 5" :showInfoData="showInfoData" :showInfoFlyToPoint="showInfoFlyToPoint" :isRead="true"/>
<IdleLandInsertModal v-if="showInfoType == 3" :showInfoData="showInfoData" :showInfoFlyToPoint="showInfoFlyToPoint" :isRead="true"/>
<IdleHouseInsertModal v-if="showInfoType == 2" :showInfoData="showInfoData" :showInfoFlyToPoint="showInfoFlyToPoint" :isRead="true"/>
<IdleFarmingInsertModal v-if="showInfoType == 1" :showInfoData="showInfoData" :showInfoFlyToPoint="showInfoFlyToPoint" :isRead="true"/>
<IdleNongjiInsertModal v-if="showInfoType == 4" :showInfoData="showInfoData" :showInfoFlyToPoint="showInfoFlyToPoint" :isRead="true"/>
</a-modal>
</div>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import Map from './Converge/index.vue';
import useDrag from './drag';
import LayerCenter from './mapComponent/layers/index.vue';
import { getDetail } from '@/api/tiankongdi/index'
import IdleWatersInsertModal from '@/views/demo/IdleWaters/InsertModal.vue'
import IdleLandInsertModal from '@/views/demo/IdleLand/InsertModal.vue'
import IdleHouseInsertModal from '@/views/demo/IdleHouse/InsertModal.vue'
import IdleFarmingInsertModal from '@/views/demo/IdleFarming/InsertModal.vue'
import IdleNongjiInsertModal from '@/views/demo/IdleNongji/InsertModal.vue'
const { style, startDrag } = useDrag();
const MapboxComponent = ref();
const showInfoData = ref()
const showInfoType = ref()
const showInfoFlyToPoint = ref()
const showInfoOpen = ref(false)
const spinning = ref(false)
const legends = ref([
{ label:'房屋', color: '#F70303' },
{ label:'土地资源', color: '#0AF703' },
{ label:'水域', color: '#0382F7' },
{ label:'农业生产设施', color: '#F4E004' },
])
onMounted(() => {
spinning.value = true
})
const changeLoading = (type: boolean) => {
spinning.value = type
}
function changeLayer(layerList) {
MapboxComponent.value.handlerChangeLayer(layerList);
}
const changeLabel = (type) => {
MapboxComponent.value.changeTiandituLabelOpen(type);
}
const handlerGetDetails = (item, flyPoint) => {
console.log('item',item)
showInfoType.value = item.Type
getDetail( {id:item.Id} , item.Type).then(res => {
console.log('res',res)
showInfoData.value = res
showInfoOpen.value = true
showInfoFlyToPoint.value = flyPoint
})
}
</script>
<style lang="less" scoped>
.full-modal {
.ant-modal {
min-width: 100vw;
top: 0px;
padding: 0px;
margin: 0px;
}
.ant-modal-content {
display: flex;
flex-direction: column;
}
.ant-modal-body {
flex: 1;
}
}
.statistical{
:deep(.ant-modal-header){
padding: 0px;
}
:deep(.ant-modal-wrap){
overflow: hidden;
pointer-events:none;
}
:deep(.dragModal){
margin: 0 !important;
.hidden-show-icon{
width: 28px;
height: 28px;
background-size: 100% 100%;
margin-left: 20px;
cursor: pointer;
}
}
}
.statistical {
// position: relative;
//
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
user-select: none;
width:100%;
height:calc(-80px + 100vh);
position:relative;
}
#bg-pan {
}
#alertOverlay::before,
#alertOverlay::after {
content: '';
position: absolute;
width: 50px;
height: 100%;
}
#alertOverlay div::before,
#alertOverlay div::after {
content: '';
position: absolute;
width: 100%;
height: 50px;
}
#alertOverlay::before {
background: linear-gradient(to right, rgba(0, 0, 0, 0.8), transparent);
top: 0;
left: 0;
transform: rotate(0deg);
}
#alertOverlay::after {
background: linear-gradient(to left, rgba(0, 0, 0, 0.8), transparent);
top: 0%;
left: 100%;
transform: rotate(0deg) translate(calc(-1 * 50px), 0px);
}
#alertOverlay div::before {
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
top: 0;
left: 0;
transform: rotate(180deg);
}
#alertOverlay div::after {
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
top: 100%;
left: 0;
transform: rotate(0deg) translate(0px, calc(-1 * 50px));
}
#alertOverlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
opacity: 1;
transition: opacity 0.5s;
pointer-events: none;
z-index: 1;
}
.alert-active {
animation: blink 0s infinite;
}
@keyframes blink {
0% {
opacity: 0;
}
50% {
opacity: 0.5;
}
100% {
opacity: 0;
}
}
.legend {
width: 220px;
padding: 10px;
position: absolute;
bottom: 20px;
right: 20px;
display: flex;
flex-wrap: wrap;
column-gap: 30px;
border-radius: 8px;
background-image: url('/statistical/left_statistical.png');
background-size: 100% 100%;
.legend-item {
width: 120px;
padding: 5px 0px;
font-size: 14px;
color: #666666;
display: flex;
flex: 48% 48%;
color: #7ebbff;
.legend-dot {
width: 14px;
height: 14px;
}
.legend-label {
margin-left: 12px;
}
}
.flh-item{
width: 120px;
}
}
/**图层控制 视频监控**/
.layer-center-container {
width:285px;
position: absolute;
}
.drag-area{
width:245px;
height:40px;
position:absolute;
top:0px;
left:0px;
z-index:99999;
cursor:move;
}
.TC-videoi-container {
position: absolute;
bottom: 48px;
right: 38px;
width: 418px;
height: 300px;
.close-button {
width: 28px;
height: 28px;
background: rgba(0, 0, 0, 0.6);
text-align: center;
line-height: 28px;
position: absolute;
top: 4px;
right: 4px;
z-index: 999999;
color: #fff;
cursor: pointer;
}
}
.selection-button {
position: absolute;
cursor: pointer;
top: 20px;
left: 220px;
width: 140px;
height: 40px;
margin-left: 20px;
background-image: url(/map/change-view-btn.png);
background-size: 100% 100%;
color: #efefef;
text-align: center;
line-height: 46px;
font-size: 14px;
}
</style>
<style>
.solid-spin.ant-spin-nested-loading > .ant-spin-blur::after {
opacity: 1;
}
.solid-spin.ant-spin-nested-loading .ant-spin{
max-height: none;
}
</style>

View File

@ -0,0 +1,16 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [118.429056,35.387537]
},
"properties": {
"name":"沂南县砖埠镇东岳庄村北可见光",
"playUrl":"http://221.2.83.254:7012/live/37130100181328000001.m3u8"
}
}
]
}

View File

@ -0,0 +1,389 @@
<template>
<div class="search-container">
<div class="search-input">
<a-input
placeholder="请输入资源编号或地址"
v-model:value="keyword"
allow-clear
@blur="onInputBlue"
></a-input>
</div>
<div class="search-button" @click="searchArea">
<img src="/statistical/search-btn.png" alt="" />
</div>
<div class="search-result-container" v-if="showSearchResult">
<div class="result-item" v-for="(item, index) in searchResult" @click="toPosition(item)">
<div class="search-icon">
<img src="/statistical/search-icon.png" alt="" />
</div>
<div class="level-0">{{ item.type?item.serialNumber: item.id }}</div>
<div class="level-1"></div>
<div class="level-2">{{ item.type? getTypeName(item.type): `${item.county} ${item.street}` }}</div>
</div>
<a-empty v-if="searchResult.length == 0" />
</div>
<!-- <div class="filter-container">
<div class="filter-name">{{ currentFilter }}</div>
<div class="filter-icon" @click="handlerChangeFilterOptions">
<CaretDownOutlined />
</div>
<div class="filter-item-container" v-if="showFilterOptions">
<div
class="filter-item"
v-for="(item, index) in filters"
:key="index"
@click="handlerCheckedFilter(item.name)"
>{{ item.name }}</div
>
</div>
</div> -->
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, defineExpose, defineEmits, watch } from 'vue';
import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons-vue';
import axios from 'axios';
// import { getLoadDroneCaseInfoDetail, getPolygonCenter } from '@/api/tiankongdi/index';
// import { homePageLoadCaseInfoTuBanList } from '@/api/demo/system';
// import { getLoadCaseInfoMineralsTuBanList } from '@/api/minerals/index';
import { WktToGeojson } from '@/components/MapboxMaps/src/WktGeojsonTransform';
import { transformGCJ2WGS } from '@/utils/EpsgTransform';
import { getPageList, getPolygonCenter } from '@/api/tiankongdi/index'
const emits = defineEmits(['toPosition', 'handlerFilter', 'flyToLocation']);
const props = defineProps(['layer', 'layerSettings']);
watch(
() => props.layerSettings,
() => {
subjectName.value = props.layerSettings.subjectname;
subjectTable.value = props.layerSettings.tablename;
Object.keys(props.layerSettings.legend[0]).forEach((item) => {
if (item != '默认') {
filters.value.push({ name: item });
}
});
},
);
const subjectName = ref();
const subjectTable = ref();
async function toPosition(item) {
if (item.location) {
let newCoord = transformGCJ2WGS(item.location[1], item.location[0]);
if (newCoord) {
emits('toPosition', [newCoord.lon, newCoord.lat]);
}
return
}
let filter = '"Id"=\'' + item.id + "'";
let point = await getPolygonCenter({ tablename: 'view_shp_idle', filter: filter });
console.log('point',point)
if (point.length > 0) {
try {
let geojson = WktToGeojson(point[0].centroid_point);
emits('toPosition', geojson.coordinates);
} catch (e) {}
}
}
const searchResult = ref<any>([]);
const filters: any = ref([
{
name: '全部',
},
]);
const showSearchResult = ref(false);
const showFilterOptions = ref(false);
const address = ref()
onMounted(() => {});
function onInputFocus() {
showSearchResult.value = true;
}
function onInputBlue() {
showSearchResult.value = false;
}
async function searchAddress(){
axios({
method: 'get',
url: `https://restapi.amap.com/v3/geocode/geo?key=ed310f0b1f6cfd93edfba42f1a09d4d9&address=`+address.value,
}).then((res) => {
if(res.data){
let location = res.data.geocodes[0].location.split(',')
emits('flyToLocation', location);
}
});
}
const keyword = ref('');
async function searchArea() {
if (keyword.value.match(/^[0-9A-Z]+(-[0-9A-Z]+)*$/)) {
searchResult.value = [];
const params = { page: 1, limit: 10, key: keyword.value};
const types = [1,2,3,5]
Promise.all(types.map(type => getPageList(params, type)))
.then(results => {
console.log('所有请求成功', results);
for(let index = 0; index < results.length; index ++){
let type = 0
switch(index){
case 0:
type = 1;
break
case 1:
type = 2;
break
case 2:
type = 3;
break
case 3:
type = 5;
break
}
results[index].items.forEach(item => {
searchResult.value.push({...item,type:type})
})
}
console.log('searchResult',searchResult.value)
showSearchResult.value = true;
})
.catch(error => {
console.error('至少有一个请求失败', error);
});
} else {
//
axios
.get(
'https://restapi.amap.com/v5/place/text?&key=ee7f561fae9249aeb971bcc661083438&keywords=' +
keyword.value +
'&region=371300&citylimit=true&page_num=1&page_size=10',
)
.then((res) => {
if (res.data.info == 'OK') {
searchResult.value = [];
res.data?.pois?.forEach((item, index) => {
let obj = {
id: item.name,
county: item.cityname,
street: item.adname,
location: item.location?.split(','),
};
searchResult.value.push(obj);
});
showSearchResult.value = true;
}
});
}
}
watch(
() => keyword.value,
(newVal) => {
if (!newVal) {
showSearchResult.value = false;
searchResult.value = [];
}
},
);
const getTypeName = (type) => {
switch(type){
case 3:
return '土地资源'
case 2:
return '房屋'
case 1:
return '农业生产设施'
case 5:
return '水域'
}
}
//
defineExpose({});
</script>
<style type="less" scoped>
.addressbox{
width: 100%;
height: 100%;
display: flex;
background-image: url('/statistical/search-bg.png');
background-size: 100% 100%;
border-top-right-radius: 8px;
position: relative;
.search-input {
flex: auto;
height: 100%;
}
.search-button {
width: 62px;
height: 36px;
border-top-right-radius: 8px;
background: #0a62c6;
text-align: center;
line-height: 36px;
cursor: pointer;
}
}
.search-container {
width: 100%;
height: 100%;
display: flex;
background-image: url('/statistical/search-bg.png');
background-size: 100% 100%;
border-top-right-radius: 8px;
position: relative;
.search-input {
flex: auto;
height: 100%;
}
.search-button {
width: 62px;
height: 36px;
border-top-right-radius: 8px;
background: #0a62c6;
text-align: center;
line-height: 36px;
cursor: pointer;
}
.search-result-container {
width: 100%;
position: absolute;
top: 36px;
left: 0px;
background: rgba(11, 39, 68, 0.8);
border: 1px solid #0a62c6;
border-top: 0px;
padding: 8px 0px;
.result-item {
width: 100%;
height: 38px;
color: #e1ecf8;
display: flex;
line-height: 38px;
padding: 0px 12px;
font-size: 14px;
.search-icon {
width: 28px;
height: 36px;
}
.level-0 {
max-width: 160px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.level-1 {
margin: 0px 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.level-2 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&:hover {
cursor: pointer;
background: #0f3863;
}
}
}
.filter-container {
position: absolute;
top: 0px;
right: -120px;
width: 100px;
border-radius: 6px;
height: 36px;
background: #0a62c6;
line-height: 36px;
color: #fff;
display: flex;
cursor: pointer;
.filter-name {
width: 70px;
text-align: center;
padding: 0px 2px;
font-size: 14px;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&::after {
content: '';
width: 2px;
height: 22px;
position: absolute;
top: 7px;
right: 0px;
background: #0751a5;
}
}
.filter-icon {
width: 30px;
line-height: 38px;
text-align: center;
}
.filter-item-container {
width: 100%;
position: absolute;
background: rgba(11, 39, 68, 0.8);
border: 1px solid #0a62c6;
top: 36px;
left: 0px;
.filter-item {
height: 36px;
text-align: center;
&:hover {
cursor: pointer;
background: #0f3863;
}
}
}
}
}
::v-deep .mapboxgl-ctrl-logo {
display: none !important;
}
::v-deep .ant-input {
background: none;
border: 0px;
color: #5e90e1;
line-height: 30px;
text-indent: 8px;
outline: none;
&::placeholder {
color: #5e90e1;
}
}
::v-deep .ant-input-affix-wrapper {
background: none;
border: 0px;
color: #5e90e1;
line-height: 30px;
text-indent: 8px;
outline: none;
}
::v-deep .anticon svg {
color: #fff !important;
}
::v-deep .ant-input::placeholder {
color: #5e90e1;
}
::v-deep .ant-empty-description {
color: #fff;
}
</style>

View File

@ -0,0 +1,63 @@
export const MAPBOX_TOKEN = "pk.eyJ1IjoibGllYmFvIiwiYSI6ImNsMXg1OHdtcTE3eDEza3FmODBmeXhldmIifQ.CYYMuikQnGHMtTNq60B_xA";
export const TINADITU_TOKEN = "b6585bc41ee16251dbe6b1af64f375d9";
export const MAP_VIEWER = {
"兰山区":{
center:{lng: 118.35182370979693, lat: 35.183090621691385},
zoom:10.048587811931847
},
"河东区":{
center:{lng: 118.59697279346507, lat: 35.18490235060048},
zoom:10,
},
"罗庄区":{
center:{lng: 118.32214269732968, lat: 34.88793861991461},
zoom:10.348587811931848,
},
"高新区":{
center:{lng: 118.22321338283429, lat: 35.02848900013085},
zoom:11.197175623863698,
},
"沂河新区":{
center:{lng: 118.56406202558459, lat: 35.02402208121399},
zoom:9.848587811931848,
},
"费县":{
center:{lng: 118.07423584053117, lat: 35.25200708547465},
zoom:9.700000000000005,
},
"平邑县":{
center:{lng: 117.7943998613617, lat: 35.397797381650626},
zoom:9.5,
},
"蒙阴县":{
center:{lng: 118.16071230795704, lat: 35.706258658571166},
zoom:9.500000000000002,
},
"沂水县":{
center:{lng: 118.7262955596875, lat: 35.875369642007094},
zoom:9.500000000000002,
},
"沂南县":{
center:{lng: 118.45908849919756, lat: 35.49273684421736},
zoom:9.748587811931854,
},
"兰陵县":{
center:{lng: 118.10841982910361, lat: 34.81986176408728},
zoom:9.597175623863698,
},
"郯城县":{
center:{lng: 118.32120708234048, lat: 34.635986538650336},
zoom:9.7,
},
"临沭县":{
center:{lng: 118.74187031029359, lat: 34.850952753798644},
zoom:9.848587811931848,
},
"莒南县":{
center:{lng: 118.96807389575793, lat: 35.186365164136504},
zoom:9.848587811931848,
}
}

View File

@ -0,0 +1,498 @@
<template>
<div :id="mapContainerName" class="map-container">
<div class="search-container-box">
<SearchComponent
@flyToLocation="flyToLocation"
@toPosition="toPosition"
@handlerFilter="handlerFilter"
:layerSettings="layerSettings"
></SearchComponent>
</div>
<div class="home-button" @click="handlerInitialize"></div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, defineExpose, defineEmits, defineProps, watch } from 'vue';
import { generateUUID } from '@/components/MapboxMaps/src/tool';
import { WktToGeojson } from '@/components/MapboxMaps/src/WktGeojsonTransform';
import mapboxgl, { Map, Popup } from 'mapbox-gl';
import { MAPBOX_TOKEN, TINADITU_TOKEN, MAP_VIEWER } from './config.js';
import axios from 'axios';
import SearchComponent from './SearchComponent.vue';
import { getAppEnvConfig } from '@/utils/env';
const { VITE_GLOB_API_URL,VITE_GLOB_LAN_API_URL } = getAppEnvConfig();
const VITE_GLOB_API_URL_VAR = ref<String>(VITE_GLOB_API_URL);
const mapContainerName = ref<String>();
mapContainerName.value = 'mapContainer-' + generateUUID();
const { VITE_GLOB_YINGXIANG_SERVER, VITE_GLOB_YAOGANYINGXIANG_SERVER } = getAppEnvConfig();
import { getUserOrgs } from '@/api/tiankongdi';
const networkType = ref("WAN");
//
import { TableOutlined, GlobalOutlined, HomeOutlined } from '@ant-design/icons-vue';
//
import { message, Modal } from 'ant-design-vue';
import U from 'mapbox-gl-utils';
import { MP } from './src/MP.js';
import { GeojsonToWkt } from './src/WktGeojsonTransform.js';
import { layers } from '@/views/sys/exception/util'
const emits = defineEmits([
'onload',
'handlerGetDetails',
'showMonitor',
'handlerQueryIntersectTif',
'changeLoading'
]);
let isZoomVisible: any = false;
let map: Map;
let mp: any = null;
const props = defineProps({
layer: Object,
defaultColor: String,
});
const layerSettings = ref<any>({});
const layerDefaultColor = ref<String>();
watch(
() => props.layer,
(val) => {
layerSettings.value = val;
layerDefaultColor.value = props.defaultColor;
},
);
const initMap = () => {
return new mapboxgl.Map({
container: mapContainerName.value,
language: 'zh-cmn',
projection: 'globe', // wgs84
style: {
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
version: 8,
sources: {
'raster-tiles': {
type: 'raster',
tiles: [
`http://t0.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=${TINADITU_TOKEN}`,
],
tileSize: 256,
minzoom: 1,
maxzoom: 17,
},
'raster-tiles-font': {
type: 'raster',
tiles: [
`https://t0.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=${TINADITU_TOKEN}`,
],
tileSize: 256,
}
},
layers: [
{
id: 'tdt-vec-tiles',
type: 'raster',
source: 'raster-tiles-font',
maxZoom: 32,
},
{
id: 'tdt-img-tiles',
type: 'raster',
source: 'raster-tiles',
maxZoom: 32,
}
// {
// id: 'tdt-wms-tiles',
// type: 'raster',
// source: 'raster-tiles-geo',
// },
],
},
maxZoom: 22,
minZoom: 8,
zoom: 8,
pitch: 0,
center: [118.30207505530701, 35.30123435040745],
});
};
function loadMapInfo(){
map = initMap();
map.on('load', () => {
//mapbox-gl-utils
U.init(map);
mp = new MP(map);
// 2D
map.easeTo({ pitch: 0, bearing: 0, duration: 1000 });
map.dragRotate.disable(); //
map.pitchWithRotate = false; //
handlerDealStreet()
handlerDealCountry();
handlerCheckUserOrgs()
getMaskData()
const sources = map.getStyle().sources;
let loadedSources = 0;
Object.keys(sources).forEach(sourceId => {
map.on('sourcedata', (e) => {
if (e.sourceId === sourceId && e.isSourceLoaded) {
loadedSources++;
if (loadedSources === Object.keys(sources).length) {
console.log('所有图层关联的数据源已加载完成');
emits('changeLoading',false)
}
}
});
});
});
}
onMounted(() => {
mapboxgl.accessToken = MAPBOX_TOKEN;
//
loadMapInfo();
})
//
const handlerInitialize = () => {
map.flyTo({
center: [118.30207505530701, 35.30123435040745], //
zoom: 8, //
pitch: 0,
bearing: 0,
duration: 1000,
});
};
function toPosition(item) {
handlerLocation(item, 17.2);
}
function handlerFilter(item) {
}
async function handlerCheckUserOrgs() {
handlerLoadPolygon();
}
function getMaskData() {
map.addLayer({
id: "linyishizhezhao",
type: "raster",
source: {
type: "raster",
tiles: [
"http://175.27.168.120:8080/geoserver/gwc/service/wms?service=WMS&version=1.1.0&request=GetMap&layers=linyishi%3Alinyishizhezhao&styles=&bbox={bbox-epsg-3857}&width=256&height=256&srs=EPSG:3857&format=image/png&TRANSPARENT=TRUE",
],
tileSize: 256,
},
layout: {
visibility: "visible",
}
});
}
const handlerGetDetails = (e) => {
emits('handlerGetDetails', e.features[0].properties, [e.lngLat.lng, e.lngLat.lat]);
}
const handlerNongjiGetDetails = (e) => {
emits('handlerGetDetails', e.features[0].properties, e.features[0].geometry.coordinates);
}
function handlerLoadPolygon(code = '', filter = '', type = '', level = '') {
let field_filter = '&field="SerialNumber","Type","Id",';
map.addSource('tianditu-label', {
type: 'raster',
tiles: [
`https://t0.tianditu.gov.cn/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=${TINADITU_TOKEN}`
],
tileSize: 256
});
map.addLayer({
id: 'tianditu-label-layer',
type: 'raster',
source: 'tianditu-label',
layout: {
visibility: 'none',
}
});
map.loadImage('/homepage/nong.png', (error, image) => {
if (error) throw error;
if (!map.hasImage('nongji-marker')) {
map.addImage('nongji-marker', image);
}
});
layers.forEach(layerItem => {
const sourceId = `${layerItem.id}Source`
map.addSource(sourceId, {
type: 'vector',
tiles: [
VITE_GLOB_API_URL_VAR.value +
'/api/IdleLand/QueryVectorTileByTable?z={z}&x={x}&y={y}&table=' +
layerItem.tableName +
field_filter
],
minzoom: 1,
maxzoom: 20,
});
if(layerItem.id == 'nongji'){
const layerId = `${layerItem.id}SourceLayer`
map.addLayer({
id: layerId,
type: 'symbol',
source: sourceId,
'source-layer': layerItem.tableName,
layout: {
'icon-image': 'nongji-marker',
'icon-size': 0.8,
'icon-allow-overlap': true,
'icon-anchor': 'bottom',
visibility: 'none'
},
});
map.on('click', layerId, handlerNongjiGetDetails);
}else{
const lineLayerId = `${layerItem.id}SourceLineLayer`
const fillLayerId = `${layerItem.id}SourceFillLayer`
map.addLayer({
id: lineLayerId,
type: 'line',
source: sourceId,
'source-layer': layerItem.tableName,
layout: {
'line-join': 'round',
'line-cap': 'round',
visibility: 'none',
},
paint: {
'line-color': layerItem.color,
'line-width': 5,
},
});
map.addLayer({
id: fillLayerId,
type: 'fill',
source: sourceId,
'source-layer': layerItem.tableName,
layout: {
visibility: 'none',
},
paint: {
'fill-color': layerItem.color,
'fill-opacity': 0.5,
},
});
map.on('click', fillLayerId, handlerGetDetails);
}
})
}
//
const handlerDealStreet = (countyName: String = '临沂市'): void => {
map.addSource("zhenjie", {
"type": "vector",
"scheme": "tms",
tiles: [
"http://175.27.168.120:8080/geoserver/gwc/service/tms/1.0.0/linyishi%3Azhenjie@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf",
],
tileSize: 512
})
map.addLayer({
id: "zhenjie",
type: "line",
source: "zhenjie",
'source-layer': 'zhenjie',
layout: {
visibility: "visible",
},
paint: {
'line-color': '#42befb', // 线
'line-width': 1, // 线
},
minzoom: 10,
maxzoom: 24,
})
map.addSource("zhenjiepoint", {
"type": "vector",
"scheme": "tms",
tiles: [
"http://175.27.168.120:8080/geoserver/gwc/service/tms/1.0.0/linyishi%3Azhenjiepoint@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf",
],
tileSize: 512,
})
map.addLayer({
id: "zhenjiepoint",
type: "symbol",
source: "zhenjiepoint",
'source-layer': 'zhenjiepoint',
layout: {
visibility: "visible",
"text-field": "{xzqmc}",
},
paint: {
'text-color': '#041B36',
'text-halo-color': '#fff',
'text-halo-width': 2,
},
minzoom: 10,
maxzoom: 24,
});
};
//
const handlerDealCountry = (countyName: String = '临沂市'): void => {
map.addSource("wmsSource", {
"type": "vector",
"scheme": "tms",
tiles: [
"http://175.27.168.120:8080/geoserver/gwc/service/tms/1.0.0/linyishi%3Axianjie@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf"
],
tileSize: 512
})
map.addLayer({
id: "xianjie",
type: "line",
source: "wmsSource",
'source-layer': 'xianjie',
layout: {
visibility: "visible",
},
paint: {
"line-color": "#6f7ff4",
"line-width": 4,
}
})
map.addSource("xianjiepoint", {
"type": "vector",
"scheme": "tms",
tiles: [
"http://175.27.168.120:8080/geoserver/gwc/service/tms/1.0.0/linyishi%3Alinyishixianjie_point@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf",
],
tileSize: 512,
})
map.addLayer({
id: "xianjiepoint",
type: "symbol",
source: "xianjiepoint",
'source-layer': 'linyishixianjie_point',
layout: {
visibility: "visible",
"text-field": "{xzqmc}",
"text-size": 18
},
paint: {
'text-color': '#041B36',
'text-halo-color': '#fff',
'text-halo-width': 2,
},
minzoom: 8,
maxzoom: 10,
});
};
//
function handlerLocation(lngLat, zoom) {
map.flyTo({
center: lngLat,
zoom: zoom,
bearing: 0,
speed: 1, //
curve: 2, // 线
essential: true,
easing(t) {
//
return t;
},
});
}
function flyToLocation(e){
map.flyTo({
center: e,
zoom: 12,
bearing: 0,
speed: 1, //
curve: 2, // 线
essential: true,
easing(t) {
//
return t;
},
});
}
const handlerChangeLayer = (list) => {
const layerIds = layers.map(item => item.id);
layerIds.forEach(id => {
const visibility = list.includes(id) ? 'visible' : 'none';
if(id == 'nongji'){
const layerId = `${id}SourceLayer`
map.setLayoutProperty(layerId, 'visibility', visibility);
}else{
const lineLayerId = `${id}SourceLineLayer`
const fillLayerId = `${id}SourceFillLayer`
map.setLayoutProperty(lineLayerId, 'visibility', visibility);
map.setLayoutProperty(fillLayerId, 'visibility', visibility);
}
})
}
const changeTiandituLabelOpen = (type) => {
map.setLayoutProperty('tianditu-label-layer', 'visibility', type ? 'visible' : 'none');
}
//
defineExpose({
handlerChangeLayer,
changeTiandituLabelOpen
});
</script>
<style type="less" scoped>
.map-container {
width: 100%;
height: 100%;
position: relative;
}
.search-container-box {
position: absolute;
width: 330px;
height: 40px;
top: 72px;
left: 73px;
z-index: 9999;
}
.map-type-switch-container {
width: 208px;
height: 40px;
position: absolute;
top: 72px;
right: 23px;
z-index: 9999;
font-size: 22px;
color: #fff;
text-align: center;
line-height: 40px;
cursor: pointer;
}
.map-type-switch-container .switch-button {
line-height: 40px;
width: 150px;
height: 40px;
float: left;
background: url(/map/change-view-btn.png);
background-size: 100% 100%;
}
.map-type-switch-container .switch-button span {
font-size: 14px;
}
.home-button {
width:40px;
height:40px;
background: url(/map/home-btn.png);
background-size: 100% 100%;
float: right;
position: relative;
text-align: center;
z-index: 10;
top: 72px;
right: 23px;
}
::v-deep .mapboxgl-ctrl-logo {
display: none !important;
}
</style>

View File

@ -0,0 +1,554 @@
import U from 'mapbox-gl-utils';
import * as turf from '@turf/turf';
type EventType = 'Point' | 'LineString' | 'Polygon';
interface GenerateGeoJSONDataInterface {
lng: number;
lat: number;
}
interface FeatureCollection {
type: string;
features: Array<any>;
}
const CIRCLE_STYLE = {
'circle-color': '#6495ED',
'circle-radius': 8,
'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff',
};
const LINE_STYLE = {
'line-color': '#6495ED',
'line-width': 3,
};
const LINE_IS_DRAW_STYLE = {
'line-dasharray': [2, 2],
'line-color': '#00FA9A',
'line-width': 3,
};
const POLYGON_STYLE = {
'fill-color': '#FAFAD2',
'fill-opacity': 0.1,
};
function typeOf(obj: any): any {
const toString: any = Object.prototype.toString;
const map: any = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object',
};
return map[toString.call(obj)];
}
function deepClone(data: any): any {
// 获取传入拷贝函数的数据类型
const type = typeOf(data);
// 定义一个返回any类型的数据
let reData: any;
// 递归遍历一个array类型数据
if (type === 'array') {
reData = [];
for (let i = 0; i < data.length; i++) {
reData.push(deepClone(data[i]));
}
} else if (type === 'object') {
//递归遍历一个object类型数据
reData = {};
for (const i in data) {
reData[i] = deepClone(data[i]);
}
} else {
// 返回基本数据类型
return data;
}
// 将any类型的数据return出去作为deepClone的结果
return reData;
}
class BaseMP {
map: any;
listeners: {};
constructor(map: any) {
this.map = map;
// 初始化mapbox工具类
U.init(this.map);
this.listeners = {};
}
//监听
on(event: EventType, callback: void) {
console.log('on', event);
this.listeners[event] = callback;
}
off(event: EventType) {
console.log('off', event);
if (this.listeners[event]) {
delete this.listeners[event];
}
}
emit(event: EventType, data: any) {
if (this.listeners[event]) {
this.listeners[event](data);
}
}
//防抖函数
debounce = (func: Function, delay: number) => {
let timer: any = null;
return (...args: any) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
};
//切换鼠标样式
_changeMouseCursor = (cursor: string = 'pointer') => {
this.map.getCanvas().style.cursor = cursor;
};
//生成geoJson
_generateGeoJSON(data: GenerateGeoJSONDataInterface[], geometryType: EventType) {
/**
* // 生成点类型的GeoJSON
* var data = [{lng: -122.4194, lat: 37.7749}, {lng: -122.408, lat: 37.791}, {lng: -122.431, lat: 37.769}];
* var geoJSON = generateGeoJSON(data, "Point");
*
* // 生成线类型的GeoJSON
* var data = [{lng: -122.4194, lat: 37.7749}, {lng: -122.408, lat: 37.791}, {lng: -122.431, lat: 37.769}];
* var geoJSON = generateGeoJSON(data, "LineString");
*
* // 生成面类型的GeoJSON
* var data = [{lng: -122.4194, lat: 37.7749}, {lng: -122.408, lat: 37.791}, {lng: -122.431, lat: 37.769}];
* var geoJSON = generateGeoJSON(data, "Polygon");
* **/
const featureCollection: FeatureCollection = {
type: 'FeatureCollection',
features: [],
};
switch (geometryType) {
case 'Point':
for (let i = 0; i < data.length; i++) {
const feature = {
type: 'Feature',
geometry: {
type: geometryType,
coordinates: [data[i].lng, data[i].lat],
},
properties: {},
};
featureCollection.features.push(feature);
}
break;
case 'LineString':
const lineFeature = {
type: 'Feature',
geometry: {
type: geometryType,
coordinates: [],
},
properties: {},
};
for (let i = 0; i < data.length; i++) {
lineFeature['geometry']['coordinates'].push([data[i].lng, data[i].lat]);
}
featureCollection.features.push(lineFeature);
break;
case 'Polygon':
const polygonFeature = {
type: 'Feature',
geometry: {
type: geometryType,
coordinates: [[]],
},
properties: {},
};
for (let i = 0; i < data.length; i++) {
polygonFeature['geometry']['coordinates'][0].push([data[i].lng, data[i].lat]);
}
featureCollection.features.push(polygonFeature);
break;
}
return featureCollection;
}
}
export class MP extends BaseMP {
drawModelChoose: { LineString: string; Point: string; Polygon: string; DEFAULT: string };
drawModel: string;
drawLocal: never[];
drawCurrentId: {
point: string; //点
line: string; //线
lastLine: string; //鼠标位置的点
polygon: string;
};
currentMouseLocation: null;
correctionMouseLocation: any;
clickCount: number;
clickTimeout: any;
constructor(map) {
super(map);
this.drawModelChoose = {
LineString: 'LineString',
Point: 'Point',
Polygon: 'Polygon',
DEFAULT: 'default',
};
this.drawModel = this.drawModelChoose.DEFAULT;
this.drawLocal = [];
this.drawCurrentId = {
point: 'current-draw-point', //点
line: 'current-draw-line', //线
lastLine: 'current-draw-last-line', //鼠标位置的点
polygon: 'current-draw-polygon',
};
this.currentMouseLocation = null; //当前鼠标位置
this.correctionMouseLocation = null; //校正后的鼠标位置
this.clickCount = 0; // 定义一个计数器
this.clickTimeout = null; // 定义一个超时变量
}
//添加或更新source
_addOrUpdateSource = (gS, id) => {
if (this.map.getSource(id)) {
this.map.getSource(id).setData(gS);
} else {
this.map.U.addGeoJSON(id, gS);
}
};
// 添加layer有则不操作
_addLayer = (layerId, sourceId, type, style) => {
// 如果图层不存在,添加图层。如果存在,不做操作
if (!this.map.getLayer(sourceId)) {
switch (type) {
case this.drawModelChoose.Point:
//addCircleLayer
this.map.U.addCircleLayer(layerId, sourceId, style);
break;
case this.drawModelChoose.LineString:
//addLineLayer
this.map.U.addLineLayer(layerId, sourceId, style);
break;
case this.drawModelChoose.Polygon:
//addPolygonLayer
this.map.U.addFillLayer(layerId, sourceId, style);
break;
default:
console.log('layer类型错误');
}
}
};
_currentDrawSource = () => {
// currentGSP 点 currentGSL 线 currentGSPL 面
const currentGSP = this._generateGeoJSON(this.drawLocal, 'Point');
this._addOrUpdateSource(currentGSP, this.drawCurrentId.point);
if (
this.drawModel === this.drawModelChoose.LineString ||
this.drawModel === this.drawModelChoose.Polygon
) {
const currentGSL = this._generateGeoJSON(this.drawLocal, 'LineString');
this._addOrUpdateSource(currentGSL, this.drawCurrentId.line);
}
if (this.drawModel === this.drawModelChoose.Polygon) {
const lastGSPL = this._generateGeoJSON(this.drawLocal, 'Polygon');
this._addOrUpdateSource(lastGSPL, this.drawCurrentId.polygon);
}
};
//绘制当前的layer
_currentDrawLayer = () => {
// 如果是点,直接绘制点
this._addLayer(this.drawCurrentId.point, this.drawCurrentId.point, 'Point', CIRCLE_STYLE);
// 如果是线,在点的基础上,加上线
// 如果是面,在线的基础上,加上面
if (
this.drawModel === this.drawModelChoose.LineString ||
this.drawModel === this.drawModelChoose.Polygon
) {
this._addLayer(this.drawCurrentId.line, this.drawCurrentId.line, 'LineString', LINE_STYLE);
}
if (this.drawModel === this.drawModelChoose.Polygon) {
this._addLayer(
this.drawCurrentId.polygon,
this.drawCurrentId.polygon,
'Polygon',
POLYGON_STYLE,
);
}
};
//最后动态的一笔
_currentDrawLastLine = () => {
if (
this.drawModel === this.drawModelChoose.LineString ||
this.drawModel === this.drawModelChoose.Polygon
) {
if (!this.drawLocal.length) {
this.map.U.removeLayer(this.drawCurrentId.lastLine);
this.map.U.removeSource(this.drawCurrentId.lastLine);
return false;
}
const startPoint = this.drawLocal[this.drawLocal.length - 1];
const endPoint = this.getDrawEndPoint();
// 添加动态线
const lastGSL = this._generateGeoJSON([startPoint, endPoint], 'LineString');
this.crossesLine(this.drawLocal, [startPoint, endPoint]);
this._addOrUpdateSource(lastGSL, this.drawCurrentId.lastLine);
this._addLayer(
this.drawCurrentId.lastLine,
this.drawCurrentId.lastLine,
'LineString',
LINE_IS_DRAW_STYLE,
);
}
if (this.drawModel === this.drawModelChoose.Polygon) {
this._currentDrawLastPolygon();
}
};
//获取矫正的最后点
getDrawEndPoint = () => {
let endPoint = null;
if (this.nearlyClientDistant()) {
endPoint = this.correctionMouseLocation;
} else {
endPoint = this.currentMouseLocation;
}
return endPoint;
};
//最后动态的一笔,跟随面生成
_currentDrawLastPolygon = () => {
// 添加动态面
const endPoint = this.getDrawEndPoint();
const drawLocalCopy = deepClone(this.drawLocal);
drawLocalCopy.push(endPoint);
drawLocalCopy.push(drawLocalCopy[0]);
const lastGSPL = this._generateGeoJSON(drawLocalCopy, 'Polygon');
this._addOrUpdateSource(lastGSPL, this.drawCurrentId.polygon);
this._addLayer(
this.drawCurrentId.polygon,
this.drawCurrentId.polygon,
'Polygon',
POLYGON_STYLE,
);
};
//鼠标点击
clickHandler = (e) => {
const _this = this;
this.clickCount++; // 每次单击事件计数器加1
if (this.clickCount === 1) {
// 如果是第一次单击
this.clickTimeout = setTimeout(function () {
// 启动超时变量
// 单击事件
_this.clickCount = 0; // 计数器清零
if (_this.nearlyClientDistant()) {
_this.drawLocal.push(_this.correctionMouseLocation);
// 如果是第一个点,那么就结束绘制
// todo 画面第一点后结束
if (
_this.drawModel === _this.drawModelChoose.Polygon &&
_this.correctionMouseLocation === _this.drawLocal[0]
) {
_this._currentDrawSource();
_this._currentDrawLayer();
_this.emit(_this.drawModel, _this.drawLocal);
_this.finishDraw();
return false;
} else {
_this.drawLocal.pop();
}
} else {
_this.drawLocal.push(e.lngLat);
}
_this._currentDrawSource();
_this._currentDrawLayer();
if (_this.drawModel === _this.drawModelChoose.Point) {
_this.drawIsFinish();
}
}, 200); // 设置超时时间为300毫秒
} else {
// 如果不是第一次单击
clearTimeout(this.clickTimeout); // 清除超时变量
this.clickCount = 0; // 计数器清零
// 在这里编写双击事件的操作
// 鼠标双击事件
if (this.drawModel === this.drawModelChoose.LineString) {
_this.drawLocal.push(e.lngLat);
_this._currentDrawSource();
_this._currentDrawLayer();
this.drawIsFinish();
}
if (this.drawModel === this.drawModelChoose.Polygon) {
_this.drawLocal.push(e.lngLat);
_this._currentDrawSource();
_this._currentDrawLayer();
this.drawIsFinish();
}
}
};
//右键点击
contextmenuHandler = (e) => {
if (this.drawLocal.length < 1) {
return false;
}
this.drawLocal.pop();
this._currentDrawSource();
this._currentDrawLayer();
this._currentDrawLastLine();
};
// 鼠标移动
mousemoveHandler = this.debounce((e) => {
this.currentMouseLocation = e.lngLat;
this._currentDrawLastLine();
}, 5);
// 判断是否吸附距离12像素
nearlyClientDistant = () => {
// 判断屏幕距离,鼠标吸附
let _this = this;
let mousePoint = [this.currentMouseLocation.lng, this.currentMouseLocation.lat];
let closestFeature = null;
let closestDistance = Infinity;
let threshold = 10; // 阈值,单位为像素
// 遍历所有特征,找到距离最近的特征
let clientPosition = _this.map.project(mousePoint);
this.drawLocal.forEach(function (feature) {
_this.map.project([feature.lng, feature.lat]);
let featurePosition = _this.map.project([feature.lng, feature.lat]);
// 计算两个点在屏幕上的像素距离
let distance = Math.sqrt(
Math.pow(clientPosition.x - featurePosition.x, 2) +
Math.pow(clientPosition.y - featurePosition.y, 2),
);
if (distance < closestDistance && distance < threshold) {
closestFeature = feature;
closestDistance = distance;
}
});
// 如果距离小于阈值,则将标注吸附到特征上
if (closestFeature) {
this._changeMouseCursor('pointer');
this.correctionMouseLocation = closestFeature;
return true;
} else {
// console.log('范围外')
this._changeMouseCursor('crosshair');
return false;
}
};
// 判断是否相交 true 相交 false 不相交
crossesLine = (line1, line2) => {
let _this = this;
if (this.drawLocal.length >= 3) {
let _line1 = line1.map((e) => {
return [e.lng, e.lat];
});
_line1.pop();
let _line2 = line2.map((e) => {
return [e.lng, e.lat];
});
//判断是否有交叉
let intersect = turf.lineIntersect(turf.lineString(_line1), turf.lineString(_line2));
if (intersect.features.length > 0) {
let coordinates = intersect.features[0].geometry.coordinates;
//判断是否交叉在点上
const isExist = _line1.some(
(item) => item[0] === coordinates[0] && item[1] === coordinates[1],
);
if (isExist) {
// console.log('no cross,穿过了交点')
_this.map.U.setProperty(this.drawCurrentId.lastLine, 'line-color', '#00FA9A');
_this.map.on('click', this.clickHandler);
} else {
// console.log('cross')
_this.map.U.setProperty(this.drawCurrentId.lastLine, 'line-color', '#DC143C');
_this._changeMouseCursor('no-drop');
_this.map.off('click', this.clickHandler);
}
} else {
// console.log('no cross,不沾边')
_this.map.U.setProperty(this.drawCurrentId.lastLine, 'line-color', '#00FA9A');
_this.map.on('click', this.clickHandler);
}
} else {
return false;
}
};
drawStart = () => {
//每次绘制都要初始化数据
this.drawLocal = [];
this.deleteDraw();
//禁用鼠标双击放大事件
this.map.doubleClickZoom.disable();
this._changeMouseCursor('crosshair');
this.map.on('click', this.clickHandler);
this.map.on('contextmenu', this.contextmenuHandler);
this.map.on('mousemove', this.mousemoveHandler);
};
draw = (shape) => {
if(this.drawModelChoose[shape]) {
this.drawModel = this.drawModelChoose[shape];
this.drawStart();
} else {
console.log(`暂无${shape}类型`);
}
};
drawIsFinish = () => {
let _this = this;
this.emit(this.drawModel, this.drawLocal);
this.finishDraw();
//恢复双击放大功能
setTimeout(() => {
_this.map.doubleClickZoom.enable();
}, 10);
};
// 结束绘制线和面
finishDraw = () => {
this.map.U.removeSource(this.drawCurrentId.lastLine);
this.map.U.removeLayer(this.drawCurrentId.lastLine);
this.drawModel = this.drawModelChoose.DEFAULT;
this.unDraw();
};
unDraw = () => {
// console.log('推出绘制模式');
this.map.off('click', this.clickHandler);
this.map.off('contextmenu', this.contextmenuHandler);
this.map.off('mousemove', this.mousemoveHandler);
this._changeMouseCursor('pointer');
this.drawLocal = [];
};
//删除绘制的内容
deleteDraw = () => {
this.map.U.removeSource([
this.drawCurrentId.line,
this.drawCurrentId.point,
this.drawCurrentId.lastLine,
this.drawCurrentId.polygon,
]);
this.map.U.removeLayer([
this.drawCurrentId.line,
this.drawCurrentId.point,
this.drawCurrentId.lastLine,
this.drawCurrentId.polygon,
]);
this.unDraw();
};
}

View File

@ -0,0 +1,18 @@
import WKT from "terraformer-wkt-parser"
import { wktToGeoJSON,geojsonToWKT } from "@terraformer/wkt"
const WktToGeojson = (wktData)=> {
// return WKT.parse(wktData)
return wktToGeoJSON(wktData);
}
const GeojsonToWkt = (geojsonData)=> {
// return WKT.convert(geojsonData)
console.log("geojsonData",geojsonData)
return geojsonToWKT(geojsonData)
}
export {WktToGeojson,GeojsonToWkt}

View File

@ -0,0 +1,80 @@
export enum MapboxConfig {
ACCESS_TOKEN = 'pk.eyJ1IjoiemhhbmcxMjM4ODk5OSIsImEiOiJja3N5Ync1cXcyMTR2Mm9xempmbGE4MnBtIn0.R-j78CRvbs6JZG-MDSoh8Q',
// ACCESS_TOKEN = "1234",
TDT_TOKEN = 'b6585bc41ee16251dbe6b1af64f375d9',
// add more config options here
}
export const MapboxDefaultStyle = {
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
version: 8,
sources: {
'dianzi': {
type: 'raster',
tiles: [
`https://t0.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=${MapboxConfig.TDT_TOKEN}`,
],
tileSize: 256,
minzoom:18,
maxzoom:24,
},
'dianzi-biaozhu': {
type: 'raster',
tiles: [
`https://t0.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=${MapboxConfig.TDT_TOKEN}`,
],
tileSize: 256,
},
'raster-tiles': {
type: 'raster',
tiles: [
`https://t0.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=${MapboxConfig.TDT_TOKEN}`,
],
tileSize: 256,
minzoom:0,
maxzoom:18,
},
},
layers: [
{
id: 'dianzi-biaozhu',
type: 'raster',
source: 'dianzi-biaozhu',
layout: {
visibility: 'visible',
},
},{
id: 'tdt-img-tiles',
type: 'raster',
source: 'raster-tiles',
minzoom: 0,
maxzoom: 18,
},{
id: 'dianzi',
type: 'raster',
source: 'dianzi',
layout: {
visibility: 'visible',
},
minzoom: 18,
maxzoom: 24,
}
],
};
export const MapControlConfig = {
DrawPoint: {
handler: 'handlerDrawPoint',
icon: '/point.png',
title: '绘制点',
},
DrawLineString: {
handler: 'handlerDrawLineString',
icon: '/line.png',
title: '绘制线',
},
DrawPolygon: {
handler: 'handlerDrawPolygon',
icon: '/polygon.png',
title: '绘制面',
},
};

View File

@ -0,0 +1,22 @@
.mapboxgl-ctrl-logo {
display: none !important;
}
.map-container {
position: relative;
}
.map-box,
.map-container {
width: 100%;
height: 100%;
}
.map-control {
position: absolute;
right: 10px;
top: 10px;
display: flex;
}
.map-control img {
width: 40px;
height: 40px;
cursor: pointer;
}

View File

@ -0,0 +1,27 @@
.mapboxgl-ctrl-logo {
display: none !important;
}
.map-container{
position: relative;
}
.map-box,
.map-container {
width: 100%;
height: 100%;
}
.map-control {
position: absolute;
right: 10px;
top: 10px;
display: flex;
img {
width: 40px;
height: 40px;
cursor: pointer;
}
img:hover {
scale: 1.2;
}
}

View File

@ -0,0 +1,62 @@
import { ControlOutlined } from '@ant-design/icons-vue';
import * as turf from '@turf/turf'
// js生成UUID
const generateUUID = ()=>{
var d = new Date().getTime(); //Timestamp
var d2 = (performance && performance.now && (performance.now()*1000)) || 0;
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16;
if(d > 0) {
r = (d + r)%16 | 0;
d = Math.floor(d/16);
} else {
r = (d2 + r)%16 | 0;
d2 = Math.floor(d2/16);
}
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
// js 针对后端post接口参数拼接到url的情况 将json参数转换为 ?key1=value1&key2=value2
const ObjectToUrl = (obj)=>{
let params = "?";
for(let item in obj){
params+=item+"="+obj[item]+"&"
}
return params;
}
// turf获取几何图形的中心
const getGeometryCenter = (geometry)=>{
let coordinates = [];
switch(geometry.geometry.type.toUpperCase()){
case "POINT":
break;
case "MULTIPOINT":
break;
case "LINESTRING":
break;
case "MULTILINESTRING":
break;
case "POLYGON":
coordinates = geometry.geometry.coordinates
break;
case "MULTIPOLYGON":
coordinates = geometry.geometry.coordinates[0]
break;
}
// let polygon = turf.polygon(coordinates);
// let center = turf.centerOfMass(polygon);
return [coordinates[0][0][0],coordinates[0][0][1]];
// return [117.732878836452,35.1320944773393]
}
export { generateUUID,ObjectToUrl,getGeometryCenter }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,148 @@
<script lang="tsx">
import type { PropType } from 'vue';
import { Result, Button } from 'ant-design-vue';
import { defineComponent, ref, computed, unref } from 'vue';
import { ExceptionEnum } from '@/enums/exceptionEnum';
import notDataSvg from '@/assets/svg/no-data.svg';
import netWorkSvg from '@/assets/svg/net-error.svg';
import { useRoute } from 'vue-router';
import { useDesign } from '@/hooks/web/useDesign';
import { useI18n } from '@/hooks/web/useI18n';
import { useGo, useRedo } from '@/hooks/web/usePage';
import { PageEnum } from '@/enums/pageEnum';
interface MapValue {
title: string;
subTitle: string;
btnText?: string;
icon?: string;
handler?: any;
status?: string;
}
export default defineComponent({
name: 'ErrorPage',
props: {
//
status: {
type: Number as PropType<number>,
default: ExceptionEnum.PAGE_NOT_FOUND,
},
title: {
type: String as PropType<string>,
default: '',
},
subTitle: {
type: String as PropType<string>,
default: '',
},
full: {
type: Boolean as PropType<boolean>,
default: false,
},
},
setup(props) {
const statusMapRef = ref(new Map<string | number, MapValue>());
const { query } = useRoute();
const go = useGo();
const redo = useRedo();
const { t } = useI18n();
const { prefixCls } = useDesign('app-exception-page');
const getStatus = computed(() => {
const { status: routeStatus } = query;
const { status } = props;
return Number(routeStatus) || status;
});
const getMapValue = computed((): MapValue => {
return unref(statusMapRef).get(unref(getStatus)) as MapValue;
});
const backLoginI18n = t('sys.exception.backLogin');
const backHomeI18n = t('sys.exception.backHome');
unref(statusMapRef).set(ExceptionEnum.PAGE_NOT_ACCESS, {
title: '403',
status: `${ExceptionEnum.PAGE_NOT_ACCESS}`,
subTitle: t('sys.exception.subTitle403'),
btnText: props.full ? backLoginI18n : backHomeI18n,
handler: () => (props.full ? go(PageEnum.BASE_LOGIN) : go()),
});
unref(statusMapRef).set(ExceptionEnum.PAGE_NOT_FOUND, {
title: '404',
status: `${ExceptionEnum.PAGE_NOT_FOUND}`,
subTitle: t('sys.exception.subTitle404'),
btnText: props.full ? backLoginI18n : backHomeI18n,
handler: () => (props.full ? go(PageEnum.BASE_LOGIN) : go()),
});
unref(statusMapRef).set(ExceptionEnum.ERROR, {
title: '500',
status: `${ExceptionEnum.ERROR}`,
subTitle: t('sys.exception.subTitle500'),
btnText: backHomeI18n,
handler: () => go(),
});
unref(statusMapRef).set(ExceptionEnum.PAGE_NOT_DATA, {
title: t('sys.exception.noDataTitle'),
subTitle: '',
btnText: t('common.redo'),
handler: () => redo(),
icon: notDataSvg,
});
unref(statusMapRef).set(ExceptionEnum.NET_WORK_ERROR, {
title: t('sys.exception.networkErrorTitle'),
subTitle: t('sys.exception.networkErrorSubTitle'),
btnText: t('common.redo'),
handler: () => redo(),
icon: netWorkSvg,
});
return () => {
const { title, subTitle, btnText, icon, handler, status } = unref(getMapValue) || {};
return (
<Result
class={prefixCls}
status={status as any}
title={props.title || title}
sub-title={props.subTitle || subTitle}
>
{{
extra: () =>
btnText && (
<Button type="primary" onClick={handler}>
{() => btnText}
</Button>
),
icon: () => (icon ? <img src={icon} /> : null),
}}
</Result>
);
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-app-exception-page';
.@{prefix-cls} {
display: flex;
flex-direction: column;
align-items: center;
.ant-result-icon {
img {
max-width: 400px;
max-height: 300px;
}
}
}
</style>

View File

@ -1,27 +1,37 @@
<template>
<div class="search-container">
<div class="search-input">
<a-input
placeholder="请输入图斑号或位置"
v-model:value="keyword"
allow-clear
@blur="onInputBlue"
></a-input>
<div class="component-container">
<!-- 分类过滤 -->
<div class="search-select">
临时用地
</div>
<div class="search-button" @click="searchArea">
<img src="/statistical/search-btn.png" alt="" />
</div>
<div class="search-result-container" v-if="showSearchResult">
<div class="result-item" v-for="(item, index) in searchResult" @click="toPosition(item)">
<div class="search-icon">
<img src="/statistical/search-icon.png" alt="" />
</div>
<div class="level-0">{{ item.id }}</div>
<div class="level-1">{{ item.county }}</div>
<div class="level-2">{{ item.street }}</div>
<!-- 搜索框 -->
<div class="search-container">
<div class="search-input">
<a-input
placeholder="请输入图斑号或位置"
v-model:value="keyword"
allow-clear
@blur="onInputBlue"
></a-input>
</div>
<div class="search-button" @click="searchArea">
<img src="/statistical/search-btn.png" alt="" />
</div>
<div class="search-result-container" v-if="showSearchResult">
<div class="result-item" v-for="(item, index) in searchResult" @click="toPosition(item)">
<div class="search-icon">
<img src="/statistical/search-icon.png" alt="" />
</div>
<div class="level-0">{{ item.id }}</div>
<div class="level-1">{{ item.county }}</div>
<div class="level-2">{{ item.street }}</div>
</div>
<a-empty v-if="searchResult.length == 0" />
</div>
<a-empty v-if="searchResult.length == 0" />
</div>
<!-- 图斑过滤 -->
<div class="filter-container">
<div class="filter-name">{{ currentFilter }}</div>
<div class="filter-icon" @click="handlerChangeFilterOptions">
@ -38,6 +48,8 @@
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, defineExpose, defineEmits, watch } from 'vue';
@ -217,6 +229,74 @@
</script>
<style type="less" scoped>
.component-container{
width:100%;
height:100%;
display: flex;
column-gap: 12px;
.search-select{
width:320px;
line-height:36px;
font-weight: bold;
height:100%;
background-image: url('/statistical/search-bg.png');
background-size: 100% 100%;
text-align: center;
color:#fff;
}
.filter-container {
width: 100px;
border-radius: 6px;
height: 36px;
background: #0a62c6;
line-height: 36px;
color: #fff;
display: flex;
cursor: pointer;
.filter-name {
width: 70px;
text-align: center;
padding: 0px 2px;
font-size: 14px;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&::after {
content: '';
width: 2px;
height: 22px;
position: absolute;
top: 7px;
right: 0px;
background: #0751a5;
}
}
.filter-icon {
width: 30px;
line-height: 38px;
text-align: center;
}
.filter-item-container {
width: 100%;
position: absolute;
background: rgba(11, 39, 68, 0.8);
border: 1px solid #0a62c6;
top: 36px;
left: 0px;
.filter-item {
height: 36px;
text-align: center;
&:hover {
cursor: pointer;
background: #0f3863;
}
}
}
}
}
.search-container {
width: 100%;
height: 100%;
@ -225,8 +305,10 @@
background-size: 100% 100%;
border-top-right-radius: 8px;
position: relative;
column-gap: 15px;
.search-input {
flex: auto;
flex:auto;
height: 100%;
}
.search-button {
@ -283,59 +365,7 @@
}
}
.filter-container {
position: absolute;
top: 0px;
right: -120px;
width: 100px;
border-radius: 6px;
height: 36px;
background: #0a62c6;
line-height: 36px;
color: #fff;
display: flex;
cursor: pointer;
.filter-name {
width: 70px;
text-align: center;
padding: 0px 2px;
font-size: 14px;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&::after {
content: '';
width: 2px;
height: 22px;
position: absolute;
top: 7px;
right: 0px;
background: #0751a5;
}
}
.filter-icon {
width: 30px;
line-height: 38px;
text-align: center;
}
.filter-item-container {
width: 100%;
position: absolute;
background: rgba(11, 39, 68, 0.8);
border: 1px solid #0a62c6;
top: 36px;
left: 0px;
.filter-item {
height: 36px;
text-align: center;
&:hover {
cursor: pointer;
background: #0f3863;
}
}
}
}
}
::v-deep .mapboxgl-ctrl-logo {

View File

@ -0,0 +1,58 @@
import { ref, onBeforeUnmount } from 'vue';
export default function useDrag() {
const isDragging = ref(false); // 是否正在拖动
const offsetX = ref(0); // 鼠标按下时的 X 坐标偏移
const offsetY = ref(0); // 鼠标按下时的 Y 坐标偏移
const style = ref({
position: 'absolute', // 设置为绝对定位
top: '200px', // 初始位置
left: '70px', // 初始位置
});
// 鼠标按下时,记录偏移量并开始拖动
const startDrag = (event: MouseEvent) => {
isDragging.value = true;
offsetX.value = event.clientX - (event.target as HTMLElement).getBoundingClientRect().left;
offsetY.value = event.clientY - (event.target as HTMLElement).getBoundingClientRect().top;
// 添加鼠标移动和鼠标松开事件监听
document.addEventListener('mousemove', onDrag);
document.addEventListener('mouseup', stopDrag);
};
// 鼠标移动时,更新元素的位置
const onDrag = (event: MouseEvent) => {
if (isDragging.value) {
if(event.clientX - offsetX.value + 280 > window.innerWidth){
return null;
}
if(event.clientY - offsetY.value + 20 > window.innerHeight){
return null;
}
style.value.left = (event.clientX - offsetX.value - 200) > 0 ? `${event.clientX - offsetX.value - 200}px` : '0px'
style.value.top = (event.clientY - offsetY.value - 100) > 0 ? `${event.clientY - offsetY.value - 100}px` : '0px';
}
};
// 鼠标松开时,停止拖动
const stopDrag = () => {
isDragging.value = false;
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('mouseup', stopDrag);
};
// 在组件卸载时移除事件监听
onBeforeUnmount(() => {
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('mouseup', stopDrag);
});
return {
style,
startDrag,
};
}

View File

@ -0,0 +1 @@
export { default as Exception } from './Exception.vue';

View File

@ -69,7 +69,9 @@
defaultColor: String,
});
const layerSettings = ref<any>({});
const layerDefaultColor = ref<String>();
watch(
() => props.layer,
(val) => {
@ -126,9 +128,9 @@
},
maxZoom: 22,
minZoom: 8,
zoom: 8,
zoom: 9.6,
pitch: 0,
center: [118.30207505530701, 35.30123435040745],
center: [117.97256,34.85481],
});
};
@ -240,6 +242,7 @@
let isLevel = orgs.find((item, index) => {
return item.name == '临沂市' || item.parentId == 0 || item.parentName == '根节点';
});
if (isLevel) {
isRootLevel.value = true;
handlerLoadPolygon('', '', '', '1');
@ -554,7 +557,7 @@
"type": "vector",
"scheme": "tms",
tiles: [
"http://175.27.168.120:8080/geoserver/gwc/service/tms/1.0.0/linyishi%3Axianjie@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf"
"http://175.27.168.120:8080/geoserver/gwc/service/tms/1.0.0/lanling%3Axianjie@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf"
],
tileSize: 512
})
@ -585,7 +588,7 @@
source: "xianjiepoint",
'source-layer': 'linyishixianjie_point',
layout: {
visibility: "visible",
visibility: "none",
"text-field": "{xzqmc}",
"text-size": 18
},
@ -669,7 +672,7 @@
source: {
type: "raster",
tiles: [
"http://175.27.168.120:8080/geoserver/gwc/service/wms?service=WMS&version=1.1.0&request=GetMap&layers=linyishi%3Alinyishizhezhao&styles=&bbox={bbox-epsg-3857}&width=256&height=256&srs=EPSG:3857&format=image/png&TRANSPARENT=TRUE",
"http://175.27.168.120:8080/geoserver/gwc/service/wms?service=WMS&version=1.1.0&request=GetMap&layers=lanling%3Alanlingxian_zhezhao&styles=&bbox={bbox-epsg-3857}&width=256&height=256&srs=EPSG:3857&format=image/png&TRANSPARENT=TRUE",
],
tileSize: 256,
},
@ -720,7 +723,7 @@
"type": "vector",
"scheme": "tms",
tiles: [
"http://175.27.168.120:8080/geoserver/gwc/service/tms/1.0.0/linyishi%3Azhenjie@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf",
"http://175.27.168.120:8080/geoserver/gwc/service/tms/1.0.0/lanling%3Azhenjie@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf",
],
tileSize: 512
})
@ -743,7 +746,7 @@
"type": "vector",
"scheme": "tms",
tiles: [
"http://175.27.168.120:8080/geoserver/gwc/service/tms/1.0.0/linyishi%3Azhenjiepoint@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf",
"http://175.27.168.120:8080/geoserver/gwc/service/tms/1.0.0/lanling%3Alanlignxian_zhenjie_point@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf",
],
tileSize: 512,
})
@ -751,7 +754,7 @@
id: "zhenjiepoint",
type: "symbol",
source: "zhenjiepoint",
'source-layer': 'zhenjiepoint',
'source-layer': 'lanlignxian_zhenjie_point',
layout: {
visibility: "visible",
"text-field": "{xzqmc}",
@ -868,8 +871,8 @@
//
const handlerInitialize = () => {
map.flyTo({
center: [118.30207505530701, 35.30123435040745], //
zoom: 8, //
center: [117.97256,34.85481], //
zoom: 9.5, //
pitch: 0,
bearing: 0,
duration: 1000,
@ -1030,6 +1033,7 @@
}
}
} else if (item.id == 'Data') {
if (map.getLayer('historyLayerLine')) {
if (item.checked) {
map.setLayoutProperty('historyLayerLine', 'visibility', 'visible');
@ -1155,7 +1159,7 @@
}
.search-container-box {
position: absolute;
width: 330px;
width: 600px;
height: 40px;
top: 72px;
left: 73px;

View File

@ -0,0 +1,163 @@
<template>
<div :class="`layer-list-container ${openLayer? '': 'layer-list-container-close'}`" >
<div class="title">
图层资源
<div class="hidden-button" @click="openLayer = !openLayer">
<img v-if="openLayer" src="/public/map/top.png" alt="">
<img v-else src="/public/map/bottom.png" alt="">
</div>
</div>
<div class="layers-container">
<p class="cate-name">数据图层</p>
<div class="case-list">
<div class="case-item" v-for="(item,index) in dataLayerList" :key="index" >
<img src="/statistical/prove-icon.png" alt="" />
<a-checkbox v-model:checked="item.checked" @change="layerChange(item,'dataLayer')" >
<span style="color:#fff;">{{item.name}}</span>
</a-checkbox>
</div>
<div class="case-item">
<img src="/statistical/prove-icon.png" alt="" />
<a-checkbox v-model:checked="tiandituLabelOpen" @change="labelChange" >
<span style="color:#fff;">地图注记</span>
</a-checkbox>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {
defineEmits,
ref,
} from 'vue'
import { layers } from '@/views/sys/exception/util'
const emits = defineEmits(['changeLayer','changeLabel'])
const openLayer = ref(true)
const tiandituLabelOpen = ref(false)
const dataLayerList = ref(layers)
const layerChange = (item,type)=>{
let showLayer = dataLayerList.value.filter(itemLayer => itemLayer.checked).map(itemLayer => itemLayer.id)
emits("changeLayer",showLayer);
}
const labelChange = () => {
emits("changeLabel",tiandituLabelOpen.value);
}
</script>
<style type="less" scoped>
.layer-list-container-close{
height: 41px !important;
}
.layer-list-container {
width: 285px;
height: 600px;
background: #041b36;
position: relative;
margin-bottom: 20px;
transition: 0.5s;
&::before {
content: '';
height: 26px;
width: 26px;
position: absolute;
bottom: 0px;
left: 0px;
background: url('/map/layer-center-left.png');
background-size: 100% 100%;
}
&::after {
content: '';
height: 26px;
width: 26px;
position: absolute;
bottom: 0px;
right: 0px;
background: url('/map/layer-center-right.png');
background-size: 100% 100%;
}
.layers-container{
width:100%;
height: calc( 100% - 60px);
overflow-y:auto;
}
.title {
width: 100%;
height: 40px;
background-image: url('/videosupervision/title.png');
background-size: 100% 100%;
line-height: 40px;
text-indent: 18px;
font-size: 18px;
font-weight: bold;
color: #fff;
position: relative;
}
.hidden-button{
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
position: absolute;
top: 0px;
right: 0px;
z-index: 2;
}
.switch-button {
float: right;
margin-right: 20px;
}
}
.case-list {
padding: 10px 24px;
overflow: auto;
.case-item {
cursor:pointer;
border-bottom: 1px dashed #1d60b4;
color:#fff;
padding: 5px 0;
height:40px;
overflow:hidden;
img {
width: 15px;
margin-right:12px;
}
span {
color: #7ebbff;
margin-left: 10px;
}
}
}
::v-deep .ant-empty-image{
height:50px;
position:relative;
}
::v-deep .ant-empty-description{
color:#fff;
}
.cate-name{
color:#fff;
padding:12px;
font-size:14px;
margin-bottom:0px;
background:rgba(0, 0, 0, 0.2)
}
::v-deeep .ant-empty-image img{
width:50px;
height:50px;
}
::v-deep .ant-checkbox-wrapper{
width: calc( 100% - 40px);
height: 20px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
</style>

View File

@ -0,0 +1,243 @@
<template>
<div class="county">
<div class="county_div">
<img class="county_div_leftButton" :src="scrollImgLeft" @click="scrollToElement('left')" />
<div ref="countyList" id="scrollContainer" class="county_div_list">
<div v-for="item in list" :key="item.id" :id="item.id" @click="clickCounty(item)">
<span v-if="!item.isClick" class="county_div_list_span_nochoose">{{ item.name }}</span>
<img
class="county_div_list_nochoose"
v-if="!item.isClick"
src="/statistical/left_county_nochoose.png"
/>
<span v-if="item.isClick" class="county_div_list_span_choose">{{ item.name }}</span>
<img
class="county_div_list_choose"
v-if="item.isClick"
src="/statistical/left_county_choose.png"
/>
</div>
</div>
<img class="county_div_rightButton" :src="scrollImgRight" @click="scrollToElement('right')" />
</div>
<div>
<img
src="/statistical/left_decorative_bottom.png"
:style="{
width: '840px',
}"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, computed, defineEmits, watch, defineExpose } from 'vue';
import { getChildrenTree } from '@/api/demo/system';
import { message } from 'ant-design-vue';
import { getUserOrgs } from '@/api/tiankongdi';
// emit
const emit = defineEmits(['countyClick']);
const props = defineProps(['bi']);
//
let countyWidth: number = 110;
const countyList = ref<HTMLElement | null>(null);
const scrollImgLeft = ref('/statistical/left_direction_left1.png');
const scrollImgRight = ref('/statistical/left_direction_right1.png');
const scrollToElement = async (type) => {
if (countyList.value) {
if (type == 'left') {
countyList.value.scrollLeft = countyList.value.scrollLeft - countyWidth;
}
if (type == 'right') {
countyList.value.scrollLeft = countyList.value.scrollLeft + countyWidth;
}
// if (countyList.value.scrollLeft < countyWidth) {
// scrollImgLeft.value = '/statistical/left_direction_left1.png';
// } else {
// scrollImgLeft.value = '/statistical/left_direction_left2.png';
// }
// if (countyList.value.scrollLeft > (list.value.length - 7 - 1) * countyWidth) {
// scrollImgRight.value = '/statistical/left_direction_right1.png';
// } else {
// scrollImgRight.value = '/statistical/left_direction_right2.png';
// }
}
};
// 访
async function handlerGetOrgs() {
let orgs = await getUserOrgs({});
let isLevel = orgs.find((item, index) => {
return item.name == '临沂市' || item.parentId == 0 || item.parentName == '根节点';
});
if (isLevel) {
//
getOptions();
} else {
//
let levesl = orgs.filter((item, index) => {
return item.parentName == '临沂市' || item.parentId == 371300;
});
list.value = levesl;
}
// clickCounty(list.value[0])
}
//
function selectCounty() {}
//
function clickCounty(record) {
list.value.forEach((item) => {
if (item.cascadeId == record.cascadeId) {
item.isClick = true;
} else {
item.isClick = false;
}
});
emit('countyClick', record);
}
//
const list = ref();
async function getOptions() {
const data = await getChildrenTree({
parentId: 371300,
});
data.forEach((item) => {
item.isClick = false;
});
list.value = data;
list.value.sort((a, b) => a.id - b.id);
let linyishi = {
id: '',
name: '临沂市',
};
console.log('listValue', list.value);
list.value.unshift(linyishi);
clickCounty(linyishi);
}
onMounted(() => {
handlerGetOrgs();
let scrollContainer = document.getElementById('scrollContainer');
scrollContainer?.addEventListener('wheel', (evt) => {
evt.preventDefault();
scrollContainer?.scrollBy({
left: evt.deltaY * 5,
behavior: 'smooth',
});
});
});
defineExpose({
selectCounty,
});
</script>
<style lang="less" scoped>
.county {
cursor: pointer;
&_div {
display: flex;
position: relative;
width: 840px;
bottom: 10px;
&_leftButton {
position: relative;
top: 42px;
left: -4px;
height: 31px;
width: 31px;
}
&_list {
display: flex;
max-width: 770px;
margin: 0px auto;
overflow-x: auto;
scroll-behavior: smooth;
&::-webkit-scrollbar {
display: none;
}
div {
width: 110px;
}
&_span_nochoose {
position: relative;
top: 30px;
width: 110px;
z-index: 999;
display: inline-flex;
align-items: center;
text-align: center;
justify-content: center;
font-family: Alibaba PuHuiTi;
font-size: 15px;
color: rgba(255, 255, 255, 0);
background: linear-gradient(0deg, #6f81d4 0.146484375%, #ffffff 86.7919921875%);
opacity: 0.6;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
color: #fff;
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
font-weight: 10000;
}
&_nochoose {
position: relative;
top: -7px;
width: 87px;
margin-left: 11.5px;
margin-right: 11.5px;
color: red;
}
&_span_choose {
position: relative;
top: 30px;
width: 110px;
z-index: 999;
display: inline-flex;
align-items: center;
text-align: center;
justify-content: center;
font-family: Alibaba PuHuiTi;
font-weight: bold;
font-size: 14px;
color: #014ce2;
}
&_choose {
position: relative;
width: 110px;
}
}
&_rightButton {
position: relative;
top: 42px;
left: 4px;
height: 31px;
width: 31px;
}
}
}
</style>

View File

@ -0,0 +1,74 @@
<template>
<div class="layerbutton">
<div
class="layerbutton_div"
:style="{
marginBottom: '15px',
}"
>
<img
id="left_layerButton_1"
:src="button_1 ? '/statistical/layer-btn1-active.png' : '/statistical/layer-btn1.png'"
@click="handlerLayerChange(1)"
/>
</div>
<div class="layerbutton_div">
<img
id="left_layerButton_2"
:src="button_2 ? '/statistical/layer-btn2-active.png' : '/statistical/layer-btn2.png'"
@click="handlerLayerChange(2)"
/>
</div>
<div class="layerbutton_div">
<img
id="left_layerButton_2"
:src="button_3 ? '/statistical/layer-btn3-active.png' : '/statistical/layer-btn3.png'"
@click="handlerLayerChange(3)"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, defineEmits, defineProps } from 'vue';
// emit
const emit = defineEmits(['handlerLayerChange']);
const button_1 = ref(false);
const button_2 = ref(true);
const button_3 = ref(false);
//
function handlerLayerChange(type: number) {
switch (type) {
case 1:
button_1.value = true;
button_2.value = false;
button_3.value = false;
break;
case 2:
button_1.value = false;
button_2.value = true;
button_3.value = false;
break;
case 3:
button_1.value = false;
button_2.value = false;
button_3.value = true;
break;
}
emit('handlerLayerChange', type);
}
</script>
<style lang="less" scoped>
.layerbutton {
position: absolute;
top: 130px;
left: 60px;
.layerbutton_div {
margin-bottom: 14px;
cursor: pointer;
img {
width: 60px;
}
}
}
</style>

View File

@ -0,0 +1,82 @@
<template>
<div class="statistical">
<img src="/statistical/left_statistical.png" />
<div class="statistical_div">
<a-row>
<a-col :span="10" @click="handlerChangePolygonType('违法')">
<span class="statistical_div_dot" style="background: #F70303" />
<span class="statistical_div_span"> 违法 </span>
</a-col>
<a-col :span="10" @click="handlerChangePolygonType('补办手续')">
<span class="statistical_div_dot" style="background: #AD04F4" />
<span class="statistical_div_span"> 补办手续 </span>
</a-col>
</a-row>
<a-row>
<a-col :span="10" @click="handlerChangePolygonType('合法')">
<span class="statistical_div_dot" style="background: #0AF703" />
<span class="statistical_div_span"> 合法 </span>
</a-col>
<a-col :span="10" @click="handlerChangePolygonType('拆除复耕')">
<span class="statistical_div_dot" style="background: #F4E004" />
<span class="statistical_div_span"> 拆除复耕 </span>
</a-col>
</a-row>
<a-row>
<a-col :span="10" @click="handlerChangePolygonType('其他')">
<span class="statistical_div_dot" style="background: #0382F7" />
<span class="statistical_div_span"> 其他 </span>
</a-col>
</a-row>
</div>
</div>
</template>
<script setup lang="ts">
import { defineEmits, defineProps } from 'vue';
const emits = defineEmits(['handlerChangePolygonType']);
const props = defineProps(['bi']);
function handlerChangePolygonType(type: String): void {
emits('handlerChangePolygonType', type);
}
</script>
<style lang="less" scoped>
.statistical {
cursor: pointer;
img {
position: relative;
height: 100px;
width: 200px;
}
&_div {
position: relative;
top: -75px;
left: 30px;
&_dot {
display: inline-block;
width: 10px;
height: 10px;
position: relative;
left: -2px;
top: -2px;
margin-right: 5px;
margin-bottom: 17px;
// border-radius: 50%;
}
&_span {
position: relative;
top: -20px;
font-family: Microsoft YaHei;
font-weight: 400;
font-size: 13px;
color: #7ebbff;
}
}
}
</style>

View File

@ -0,0 +1,156 @@
<template>
<div class="uav-container">
<div class="title">
视频监控
</div>
<div v-show="isShowPlayer">
<video
class="TCPlayer-video-contaiiner"
id="TCPlaeyrContainer"
width="235px"
height="178px"
autoplay
preload="auto"
playsinline
muted
webkit-playsinline
></video>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref,onMounted,defineProps,onBeforeUnmount} from 'vue';
const props = defineProps(["playUrl"])
let player = null;
const isShowPlayer = ref(false);
function handlerPlayVideo(item){
isShowPlayer.value = true;
if (player) {
player.src(props.playUrl);
} else {
player = TCPlayer("TCPlaeyrContainer", {});
player.src(props.playUrl);
}
}
//
onMounted(()=>{
setTimeout(function(){
handlerPlayVideo(123);
},1000)
})
//
onBeforeUnmount(()=>{
player.dispose();
player = null;
})
</script>
<style type="less" scoped>
.uav-container{
width: 418px;
height: 300px;
background:#041B36;
position: relative;
.title{
width:100%;
height:40px;
background-image:url("/videosupervision/title.png");
background-size:100% 100%;
line-height:40px;
text-indent:18px;
font-size:18px;
font-weight:bold;
color:#fff;
}
.switch-button{
float:right;
margin-right:20px;
}
.uav-list-container{
width:100%;
height: calc( 100% - 60px);
overflow:auto;
padding:20px 20px;
position:relative;
z-index:999;
.uav-empty{
width:100%;
height:100%;
text-align:center;
color:#999;
img{
width:120px;
margin:40px 0px 18px 0px;
}
}
.uav-item{
width:100%;
height:40px;
padding:0px 15px;
line-height:40px;
color:#f1f1f1;
display:flex;
background:rgba(0,0,0,0.2);
&:hover{
background:rgba(0,0,0,0.4);
}
div{
flex:1;
}
.position{
max-width:30px;
cursor:pointer;
}
.play{
max-width:30px;
cursor:pointer;
}
}
}
.TCPlayer-video-contaiiner{
width: calc( 100% - 30px);
height: calc( 100% - 70px);
position:absolute;
margin:15px;
top:40px;
right:0px;
z-index:1000;
background:#041B36;
.video-contain {
width: 100%;
height: 100%;
}
}
.close-video-button{
position:absolute;
top:40px;
right:10px;
background:rgba(0,0,0,0.3);
z-index:100100;
color:#fff;
padding:10px;
cursor:pointer;
&:hover{
color:#408eff;
}
}
}
</style>

View File

@ -0,0 +1,138 @@
<template>
<div class="county-container">
<div class="slider-container" @mousedown="startDrag" @mouseup="endDrag" @mousemove="onDrag"
@touchstart="startDrag" @touchend="endDrag" @touchmove="onDrag">
<div class="slider-content" :style="{ transform: `translateX(${translateX}px)` }">
<!-- 滑动内容项 -->
<div class="slider-item" v-for="(item, index) in list" :key="index" :style="{background:url('/statistical/left_county_nochoose.png')}">
<div calss="county-name">{{ item.name }}</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, computed, defineEmits, watch, defineExpose } from 'vue';
import { getChildrenTree } from '@/api/demo/system';
import { getUserOrgs } from '@/api/tiankongdi';
// emit
const emit = defineEmits(['countyClick']);
//
const list = ref();
// 访
async function handlerGetOrgs() {
let orgs = await getUserOrgs({});
let isLevel = orgs.find((item, index) => {
return item.name == '临沂市' || item.parentId == 0 || item.parentName == '根节点';
});
if (isLevel) {
//
getOptions();
} else {
//
let levesl = orgs.filter((item, index) => {
return item.parentName == '临沂市' || item.parentId == 371300;
});
list.value = levesl;
}
// clickCounty(list.value[0])
}
async function getOptions() {
const data = await getChildrenTree({
parentId: 371300,
});
data.forEach((item) => {
item.isClick = false;
});
list.value = data;
let linyishi = {
id: '',
name: '临沂市',
};
console.log("countyList",list.value);
list.value.unshift(linyishi);
clickCounty(linyishi);
}
//
function selectCounty() {}
//
function clickCounty(record) {
list.value.forEach((item) => {
if (item.cascadeId == record.cascadeId) {
item.isClick = true;
} else {
item.isClick = false;
}
});
emit('countyClick', record);
}
onMounted(() => {
handlerGetOrgs();
});
defineExpose({
selectCounty,
});
const isDragging = ref(false);
const startX = ref(0);
const translateX = ref(0);
const currentTranslateX = ref(0);
const items = ref(['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'])
function startDrag(event) {
isDragging.value = true;
startX.value = event.type.includes('mouse') ? event.clientX : event.touches[0].clientX;
currentTranslateX.value = translateX.value;
}
function endDrag() {
isDragging.value = false;
currentTranslateX.value = translateX.value;
}
function onDrag(event) {
if (!isDragging.value) return;
const currentX = event.type.includes('mouse') ? event.clientX : event.touches[0].clientX;
const deltaX = currentX - startX.value;
translateX.value = currentTranslateX.value + deltaX;
}
</script>
<style type="less" scoped>
.county-container{
position:absolute;
bottom:50px;
left:300px;
z-index:999999;
width:840px;
height:80px;
background:rgba(0,0,0,0.4);
.slider-container {
width: 100%;
overflow: hidden;
position: relative;
cursor: grab;
}
.slider-content {
display: flex;
transition: transform 0.3s ease;
}
.slider-item {
min-width: 100px;
margin: 0px 0px;
background-color: #ddd;
padding: 20px;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,324 @@
<template>
<div class="screen-header-container">
<div class="screen-title">
<img src="/statistical/logo.png" alt="" /> {{ t('sys.subject.header_title') }}
</div>
<span class="screen-currentTime">
<span class="time">{{ currentTime }}</span>
&nbsp; &nbsp; &nbsp;
<span class="weather">{{ Weather }}</span>
</span>
<div class="left-category-container">
<div
class="category-item"
v-for="(item, index) in left_categorys"
:key="index"
:style="{
left: `${-(index * 26)}px`,
'background-image': item.hovered
? `url(/statistical/category-left-active.png)`
: `url(/statistical/category-left.png)`,
}"
@click="getHome(item)"
@mouseover="item.hovered = true"
@mouseout="item.hovered = false"
>
{{ item.title }}
</div>
</div>
<div class="right_category-container">
<div
class="category-item"
v-for="(item, index) in right_categorys"
:key="index"
:style="{
left: `${-(index * 26 - 55)}px`,
'background-image': item.hovered
? `url(/statistical/category-right-active.png)`
: `url(/statistical/category-right.png)`,
}"
@click="getHome(item)"
@mouseover="item.hovered = true"
@mouseout="item.hovered = false"
>
{{ item.title }}
</div>
</div>
<div class="user-info" style="z-index: 9999">
<span style="color: #325e95">{{ fireUserLoginName }}</span>
&nbsp;
<span style="color: #ddd" @click="logout">退</span>
</div>
</div>
<div class="right_category-container">
<div
v-for="(item, index) in right_categorys"
:key="item.id"
class="category-item"
:style="{
left: `${-(index * 26)}px`,
'background-image': item.hovered
? `url(/statistical/category-right-active.png)`
: `url(/statistical/category-right.png)`,
}"
@click="getHome(item)"
@mouseover="item.hovered = true"
@mouseout="item.hovered = false"
>
{{ item.title }}
</div>
</div>
<div class="user-info" style="z-index: 9999">
<span style="color: #325e95">{{ fireUserLoginName }}</span>
&nbsp;&nbsp;&nbsp;
<span style="color: #ddd" @click="logout">退</span>
</div>
</template>
<script lang="ts" setup>
import { ref, defineEmits, onMounted, onUnmounted, watchEffect, h } from 'vue';
import axios from 'axios';
import dayjs from 'dayjs';
import { useI18n } from '@/hooks/web/useI18n';
const { t } = useI18n();
import { message } from 'ant-design-vue';
import { getSpecialData } from '@/api/demo/system';
import { useRouter } from 'vue-router';
import { PageEnum } from '@/enums/pageEnum';
import { useUserStore } from '@/store/modules/user';
const userStore = useUserStore();
const router = useRouter();
import { useMessage } from '@/hooks/web/useMessage';
import { useMultipleTabStore } from '@/store/modules/multipleTab';
const { createConfirm } = useMessage();
//
const left_categorys = ref([]);
const right_categorys = ref([]);
async function getMenu() {
const res: any = await getSpecialData();
console.log('res123', res);
//
let category: any = {
title: '综合监管',
isDevelop: false,
hovered: false,
};
left_categorys.value?.push(category);
res?.forEach((item, index) => {
if (index < 2) {
//
item.hovered = false;
left_categorys.value?.push(item);
} else if (index >= 2 && index < 5) {
//
item.hovered = false;
right_categorys.value?.push(item);
}
});
}
//
const fireUserLoginName = ref(localStorage.getItem('fireUserLoginName'));
function getFireUserLoginName() {
fireUserLoginName.value = localStorage.getItem('fireUserLoginName')
? localStorage.getItem('fireUserLoginName')
: '';
}
const tabStore = useMultipleTabStore();
function logout() {
createConfirm({
iconType: 'warning',
title: () => h('span', t('sys.navigation.logoutTip')),
content: () => h('span', t('sys.navigation.logoutMessage')),
onOk: async () => {
tabStore.resetState();
await router.replace(PageEnum.SUBJECT_HOME);
},
});
}
async function getHome(item) {
console.log(window.innerWidth);
if (!item.isDevelop) {
message.warn(item.title + '系统暂未开放!');
return;
}
if (item.isExternal) {
if (item.linkOrApi) {
window.location.href = item.linkOrApi;
} else {
message.warn('系统外部链接未配置!');
return;
}
} else {
if (!item.linkOrApi) {
item.linkOrApi = PageEnum.WELCOME_HOME;
}
userStore.setSubject(item.id, item.title, item.logoTitle, item.linkOrApi);
}
}
//
const currentTime = ref(dayjs().locale('zh-cn').format('YYYY年M月D日 dddd HH:mm:ss'));
const Weather = ref('');
async function getWeather() {
try {
// API
const response = await axios.get('https://api.map.baidu.com/weather/v1/', {
params: {
district_id: 371300,
data_type: 'all',
ak: 'qbMvuMbWe1fJnOBNLJFsjoeHC3wfWkoB',
},
timeout: 15000,
});
Weather.value = response?.result?.now?.text;
} catch (error) {
//
// http://www.tianqiapi.com/index/doc?version=v63
// appidappsecret
axios({
method: 'get',
url: `http://v0.yiketianqi.com/api?unescape=1&version=v63&appid=88136471&appsecret=Hw4GLOcA&city=临沂`,
}).then((res) => {
Weather.value = res.data.wea;
});
}
}
function updateTime() {
currentTime.value = dayjs().locale('zh-cn').format('YYYY年M月D日 dddd HH:mm');
}
//
watchEffect(() => {
const intervalId = setInterval(updateTime, 1000);
//
onUnmounted(() => clearInterval(intervalId));
});
onUnmounted(() => {
updateTime();
});
onMounted(() => {
getWeather();
getMenu();
getFireUserLoginName();
});
</script>
<style lang="less" scoped>
.screen-header-container {
width: 100%;
height: 100px;
// background-image: url('/statistical/header.png');
// background-size: 100% 96px;
background:
// linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.2) 80%, rgba(0, 0, 0, 0) 100%)
// no-repeat top left / 100% 90px,
url('/statistical/mask.png') no-repeat top left / 100% 100px,
url('/statistical/header.png') no-repeat top left / 100% 96px;
position: absolute;
top: 0px;
left: 0px;
z-index: 3;
height: 94px;
.screen-title {
width: 682px;
// height: 96px;
line-height: 90px;
font-family: Alibaba PuHuiTi;
font-weight: bold;
font-size: 45px;
color: #8be5ee;
background: linear-gradient(0deg, #c6f3ff 0%, #f3feff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin: 0px auto;
text-align: center;
img {
position: relative;
top: -5px;
}
}
.screen-currentTime {
position: relative;
left: 50px;
top: -60px;
width: 344px;
height: 14px;
font-family: DengXian;
font-weight: 400;
font-size: 14px;
color: #8bd8ff;
line-height: 3px;
font-style: italic;
display: flex;
align-items: center;
}
.time,
.weather {
display: inline-block;
}
.separator {
margin: 0 50px;
}
.left-category-container {
position: absolute;
top: 50px;
left: 50%;
transform: translate(calc(-50% - 570px), 0);
.category-item {
float: left;
width: 138px;
height: 32px;
font-size: 14px;
line-height: 32px;
text-align: center;
background-image: url('/statistical/category-left.png');
background-size: 138px 32px;
color: #fff;
position: relative;
cursor: pointer;
}
}
.right_category-container {
position: absolute;
top: 50px;
right: 50%;
transform: translate(calc(50% + 570px), 0);
.category-item {
float: left;
width: 138px;
height: 32px;
font-size: 14px;
line-height: 32px;
text-align: center;
background-image: url('/statistical/category-right.png');
background-size: 138px 32px;
color: #fff;
cursor: pointer;
position: relative;
}
}
.user-info {
width: 120px;
color: #e1e1e1;
position: absolute;
top: 48px;
right: 50px;
font-size: 14px;
z-index: 9;
&:hover {
cursor: pointer;
color: #8be5ee;
}
}
}
</style>

View File

@ -0,0 +1,37 @@
export const layers = [
{
id: 'house',
name:"房屋",
checked:false,
tableName: 'view_shp_idle_house',
color: '#F70303',
},
{
id: 'land',
name:"土地资源",
checked:false,
tableName: 'view_shp_idle_land',
color: '#0AF703',
},
{
id: 'waters',
name:"水域",
checked:false,
tableName: 'view_shp_idle_waters',
color: '#0382F7',
},
{
id: 'farming',
name:"农业生产设施",
checked:false,
tableName: 'view_shp_idle_farming',
color: '#F4E004',
},
{
id: 'nongji',
name:"农机组织",
checked:false,
tableName: 'view_shp_idle_nongji',
color: '#F70303',
},
]