<template>
|
<BaseMap></BaseMap>
|
<div class="wrap p-events-none">
|
<el-row
|
class="p-events-none bottom-left-wrap"
|
align="middle"
|
:style="leftCardWrapStyle"
|
>
|
<div class="card-left" ref="refLeftCard">
|
<div v-show="leftCardShow" class="p-events-auto">
|
<div ref="refLeftCardTitle" class="p-8">
|
<el-row justify="space-between">
|
<el-text size="large">巡查场景</el-text>
|
<el-switch
|
v-model="showMarkName"
|
inline-prompt
|
style="
|
--el-switch-on-color: #13ce66;
|
--el-switch-off-color: #ff4949;
|
"
|
active-text="显示名称"
|
inactive-text="显示编号"
|
/>
|
</el-row>
|
</div>
|
<el-row class="p-events-auto top-left-wrap p-h-8">
|
<FYOptionLocation
|
label=""
|
:initValue="false"
|
:checkStrictly="true"
|
:allOption="false"
|
style="width: 300px"
|
:filters="locationFilter"
|
v-model:value="locationStatus"
|
></FYOptionLocation>
|
<FYOptionScene
|
label=""
|
:allOption="true"
|
:type="2"
|
v-model:value="scenetype"
|
></FYOptionScene>
|
<FYOptionConstructionStatus
|
v-show="showSceneStatus"
|
label=""
|
:initValue="true"
|
v-model:value="sceneStatus"
|
></FYOptionConstructionStatus>
|
<FYOptionConstructionStage
|
v-show="showSceneStage"
|
label=""
|
:initValue="true"
|
v-model:value="sceneStage"
|
></FYOptionConstructionStage>
|
<slot name="left-top"></slot>
|
</el-row>
|
<el-row class="p-h-8 m-b-4" justify="end">
|
<el-input
|
v-model="filterText"
|
icon="Search"
|
style="width: 100%"
|
placeholder="输入关键字,按回车键搜索"
|
clearable
|
/>
|
<el-space :size="0">
|
<el-button text @click="sortScene('problem')" size="small">
|
问题
|
<el-icon class="el-icon--right"
|
><component :is="sortProblem.icon"></component
|
></el-icon>
|
</el-button>
|
<el-button text @click="sortScene('change')" size="small">
|
整改
|
<el-icon class="el-icon--right"
|
><component :is="sortChange.icon"></component
|
></el-icon>
|
</el-button>
|
</el-space>
|
</el-row>
|
<el-scrollbar :height="scrollHeight" class="scrollbar">
|
<div v-if="filteredSceneList.length > 0">
|
<div
|
v-for="s in filteredSceneList"
|
:key="s.guid"
|
justify="space-between"
|
class="p-v-4 scene-item"
|
>
|
<el-row justify="space-between">
|
<el-text truncated style="flex: 1">
|
{{ s.index + '. ' + s.name }}
|
</el-text>
|
<el-checkbox
|
:model-value="s._checked"
|
@change="handleSceneCheck(s)"
|
/>
|
</el-row>
|
<el-row justify="space-between">
|
<el-text truncated style="flex: 1" size="small" type="info">
|
地址:{{ s.location }}
|
</el-text>
|
</el-row>
|
<el-space style="margin-top: 4px">
|
<el-tag type="info" effect="plain" size="small">
|
<el-icon class="is-loading" v-if="s._loading">
|
<Loading color="#409eff" />
|
</el-icon>
|
<span v-else>{{ s._time }}</span>
|
</el-tag>
|
|
<el-tag type="primary" effect="plain" size="small">
|
<span>整改/问题:</span>
|
<el-icon class="is-loading" v-if="s._loading">
|
<Loading color="#409eff" />
|
</el-icon>
|
<span v-else>{{ `${s._changeNum} / ${s._proNum}` }}</span>
|
</el-tag>
|
<el-tag type="warning" effect="plain" size="small">
|
<span>整改率:</span>
|
<el-icon class="is-loading" v-if="s._loading">
|
<Loading color="#409eff" />
|
</el-icon>
|
<span v-else>{{
|
ratioFormat(s._changeNum, s._proNum)
|
}}</span>
|
</el-tag>
|
</el-space>
|
<el-row justify="space-between" style="margin-top: 4px">
|
<el-space>
|
<el-tag type="info" effect="plain" size="small">
|
{{ s.type }}
|
</el-tag>
|
<el-tag
|
:type="s.extension1 == '0' ? 'info' : 'success'"
|
size="small"
|
>
|
{{ onlineFormat(s.extension1) }}
|
</el-tag>
|
<el-tag type="info" effect="plain" size="small">
|
{{ s.townname }}
|
</el-tag>
|
<el-tag
|
v-show="s._stage"
|
type="info"
|
effect="plain"
|
size="small"
|
>
|
{{ s._stage }}
|
</el-tag>
|
<el-tag
|
v-show="s._status"
|
type="info"
|
effect="plain"
|
size="small"
|
>
|
{{ s._status }}
|
</el-tag>
|
</el-space>
|
<el-space>
|
<el-icon
|
class="cursor-p"
|
:color="'rgb(121, 187, 255)'"
|
@click="locateTo(s)"
|
>
|
<LocationInformation />
|
</el-icon>
|
<el-icon
|
class="cursor-p"
|
:color="
|
s._visible ? 'rgb(121, 187, 255)' : 'rgb(200, 201, 204)'
|
"
|
@click="handleVisibleChange(s)"
|
>
|
<View />
|
</el-icon>
|
</el-space>
|
</el-row>
|
</div>
|
</div>
|
<el-empty v-else description="无相关场景" />
|
</el-scrollbar>
|
</div>
|
</div>
|
<el-button
|
class="close-btn-right p-events-auto"
|
type="success"
|
size="small"
|
:icon="leftCardShow ? 'ArrowLeft' : 'ArrowRight'"
|
@click="leftCardShow = !leftCardShow"
|
></el-button>
|
</el-row>
|
</div>
|
</template>
|
<script setup>
|
import { ref, watch, computed, onMounted, reactive } from 'vue';
|
import { map, AMap, onMapMounted } from '@/utils/map/index';
|
import marks from '@/utils/map/marks';
|
import mapUtil from '@/utils/map/util';
|
import timeUtil from '@/utils/time-util';
|
import { sceneIcon } from '@/assets/scene-icon';
|
import districtSearch from '@/utils/map/districtsearch.js';
|
import problemApi from '@/api/fysp/problemApi';
|
import sceneApi from '@/api/fysp/sceneApi';
|
|
import { DCaret, CaretTop, CaretBottom } from '@element-plus/icons-vue';
|
|
const startHtml = `<div class="el-badge">`;
|
const endHtml = `<sup
|
class="el-badge__content el-badge__content--success is-fixed"
|
style="margin-top: 2px; margin-right: 2px"
|
>
|
<div class="custom-content">
|
<i class="el-icon" style="font-size: 8px">
|
<svg
|
xmlns="http://www.w3.org/2000/svg"
|
viewBox="0 0 1024 1024"
|
>
|
<path
|
fill="currentColor"
|
d="M77.248 415.04a64 64 0 0 1 90.496 0l226.304 226.304L846.528 188.8a64 64 0 1 1 90.56 90.496l-543.04 543.04-316.8-316.8a64 64 0 0 1 0-90.496"
|
></path>
|
</svg>
|
</i>
|
</div>
|
</sup>
|
</div>`;
|
//
|
const markContentHtml = (sceneType, checked) => {
|
const imgHtml = `<img
|
style="width: 30px; height: 30px"
|
src="${sceneIcon(sceneType)}"
|
/>`;
|
if (checked) {
|
return startHtml + imgHtml + endHtml;
|
} else {
|
return imgHtml;
|
}
|
};
|
|
const props = defineProps({
|
// 选中的场景
|
modelValue: {
|
type: Array,
|
default: () => []
|
},
|
// 场景点位信息
|
data: Array
|
});
|
|
const emits = defineEmits(['update:modelValue']);
|
|
onMounted(() => {
|
// setTimeout(() => {
|
// scrollHeight.value =
|
// refLeftCard.value.offsetHeight -
|
// refLeftCardTitle.value.offsetHeight +
|
// 'px';
|
// }, 1000);
|
onMapMounted(() => {
|
sceneApi.findScene({ typeid: 19 }).then((res) => {
|
res.forEach((d) => {
|
// 创建场景地图标注
|
const mark = marks.createMarker({
|
position: [d.longitude, d.latitude],
|
// img: sceneIcon(d.typeid),
|
content: markContentHtml(d.typeid),
|
label: d.name,
|
title: d.name,
|
extData: d
|
});
|
//创建圆形 Circle 实例
|
[1000].forEach((r) => {
|
var circle = new AMap.Circle({
|
center: new AMap.LngLat(d.longitude, d.latitude), //圆心
|
radius: r, //半径
|
borderWeight: 1, //描边的宽度
|
strokeColor: '#FF33FF', //轮廓线颜色
|
strokeOpacity: 1, //轮廓线透明度
|
strokeWeight: 1, //轮廓线宽度
|
fillOpacity: 0, //圆形填充透明度
|
strokeStyle: 'dashed', //轮廓线样式
|
strokeDasharray: [10, 10],
|
fillColor: '#1791fc', //圆形填充颜色
|
zIndex: 50 //圆形的叠加顺序
|
});
|
map.add(circle);
|
});
|
map.add(mark);
|
});
|
});
|
sceneApi.findScene({ typeid: 20 }).then((res) => {
|
res.forEach((d) => {
|
// 创建场景地图标注
|
const mark = marks.createMarker({
|
position: [d.longitude, d.latitude],
|
// img: sceneIcon(d.typeid),
|
content: markContentHtml(d.typeid),
|
label: d.name,
|
title: d.name,
|
extData: d
|
});
|
map.add(mark);
|
//创建圆形 Circle 实例
|
[1000].forEach((r) => {
|
var circle = new AMap.Circle({
|
center: new AMap.LngLat(d.longitude, d.latitude), //圆心
|
radius: r, //半径
|
borderWeight: 1, //描边的宽度
|
strokeColor: '#FF33FF', //轮廓线颜色
|
strokeOpacity: 1, //轮廓线透明度
|
strokeWeight: 1, //轮廓线宽度
|
fillOpacity: 0, //圆形填充透明度
|
strokeStyle: 'dashed', //轮廓线样式
|
strokeDasharray: [10, 10],
|
fillColor: '#1791fc', //圆形填充颜色
|
zIndex: 50 //圆形的叠加顺序
|
});
|
map.add(circle);
|
});
|
});
|
});
|
});
|
});
|
|
const refLeftCard = ref();
|
const refLeftCardTitle = ref();
|
|
const leftCardShow = ref(true);
|
const leftCardWrapStyle = ref();
|
const scrollHeight = ref('60vh');
|
|
let allMarkViews = [];
|
let markViewList = [];
|
const showMarkName = ref(false);
|
|
const scenetype = ref();
|
const locationStatus = ref({});
|
const sceneStatus = ref();
|
const sceneStage = ref();
|
const showSceneStatus = ref(false);
|
const showSceneStage = ref(false);
|
|
// 问题和整改的排序状态, sort: 0:没有排序;1:升序;2:降序
|
const sortProblem = ref({ sort: '0', icon: DCaret });
|
const sortChange = ref({ sort: '0', icon: DCaret });
|
|
// 选中的场景
|
const selectedSceneList = ref(props.modelValue);
|
|
let allSceneList = ref(props.data);
|
const filterText = ref('');
|
|
// 修改 filteredSceneList 计算属性,添加排序逻辑
|
const filteredSceneList = computed(() => {
|
const _filter = allSceneList.value.filter((v) => {
|
v._visible = true;
|
selectedSceneList.value.find((s) => s.guid == v.guid)
|
? (v._checked = true)
|
: (v._checked = false);
|
return _filterScene(v);
|
});
|
|
// 排序逻辑
|
_filter.sort((a, b) => {
|
// 首先根据 sortProblem 排序
|
if (sortProblem.value.sort !== '0') {
|
const aVal = a._proNum || 0;
|
const bVal = b._proNum || 0;
|
|
if (sortProblem.value.sort === '1') {
|
// 升序
|
return aVal - bVal;
|
} else {
|
// 降序
|
return bVal - aVal;
|
}
|
}
|
|
// 如果 sortProblem 没有排序,再根据 sortChange 排序
|
if (sortChange.value.sort !== '0') {
|
const aVal = a._changeNum || 0;
|
const bVal = b._changeNum || 0;
|
|
if (sortChange.value.sort === '1') {
|
// 升序
|
return aVal - bVal;
|
} else {
|
// 降序
|
return bVal - aVal;
|
}
|
}
|
|
// 默认按索引排序
|
return a.index - b.index;
|
});
|
|
return _filter;
|
});
|
|
const locationFilter = computed(() => {
|
if (props.data.length > 0) {
|
const scene = props.data[0];
|
return [scene.provincename, scene.cityname, scene.districtname];
|
} else {
|
return [];
|
}
|
});
|
|
function _filterScene(s) {
|
return (
|
(scenetype.value == undefined ||
|
scenetype.value.value == null ||
|
s.typeid + '' == scenetype.value.value) &&
|
(locationStatus.value.pCode == null ||
|
s.provincecode == locationStatus.value.pCode) &&
|
(locationStatus.value.cCode == null ||
|
s.citycode == locationStatus.value.cCode) &&
|
(locationStatus.value.dCode == null ||
|
s.districtcode == locationStatus.value.dCode) &&
|
(locationStatus.value.tCode == null ||
|
s.towncode == locationStatus.value.tCode) &&
|
(sceneStatus.value == '0' || s._status == sceneStatus.value) &&
|
(sceneStage.value == '0' || s._stage == sceneStage.value) &&
|
(s.name.indexOf(filterText.value) != -1 ||
|
s.index + '' == filterText.value ||
|
s.location.indexOf(filterText.value) != -1)
|
);
|
}
|
|
function sortScene(sortType) {
|
if (sortType === 'problem') {
|
// 切换问题数量排序状态
|
if (sortProblem.value.sort === '0') {
|
sortProblem.value.sort = '1';
|
sortProblem.value.icon = CaretTop;
|
// 重置整改数量排序
|
sortChange.value.sort = '0';
|
sortChange.value.icon = DCaret;
|
} else if (sortProblem.value.sort === '1') {
|
sortProblem.value.sort = '2';
|
sortProblem.value.icon = CaretBottom;
|
} else {
|
sortProblem.value.sort = '0';
|
sortProblem.value.icon = DCaret;
|
}
|
} else if (sortType === 'change') {
|
// 切换整改数量排序状态
|
if (sortChange.value.sort === '0') {
|
sortChange.value.sort = '1';
|
sortChange.value.icon = CaretTop;
|
// 重置问题数量排序
|
sortProblem.value.sort = '0';
|
sortProblem.value.icon = DCaret;
|
} else if (sortChange.value.sort === '1') {
|
sortChange.value.sort = '2';
|
sortChange.value.icon = CaretBottom;
|
} else {
|
sortChange.value.sort = '0';
|
sortChange.value.icon = DCaret;
|
}
|
}
|
}
|
|
watch(
|
() => props.data,
|
(nV, oV) => {
|
if (nV != oV) {
|
// setTimeout(() => {
|
createSceneMarks();
|
filterMarkViews(true);
|
if (nV?.length > 0) {
|
const scene = nV[0];
|
locationStatus.value = {
|
pCode: scene.provincecode,
|
pName: scene.provincename,
|
cCode: scene.citycode,
|
cName: scene.cityname,
|
dCode: scene.districtcode,
|
dName: scene.districtname
|
};
|
}
|
allSceneList.value = nV;
|
allSceneList.value?.forEach((d) => {
|
if (!d._loaded) {
|
d._loading = true;
|
problemApi
|
.getBySceneMonth({
|
sceneId: d.guid
|
})
|
.then((res) => {
|
const { first: subtasks, second: problems } = res.data;
|
d._proNum = problems?.length ?? 0;
|
d._changeNum = problems?.filter((d) => d.ischanged).length ?? 0;
|
d._time = timeUtil.formatYMD(subtasks?.[0]?.planstarttime);
|
})
|
.finally(() => {
|
d._loading = false;
|
d._loaded = true;
|
});
|
}
|
});
|
// }, 1000);
|
}
|
},
|
{ immediate: true }
|
);
|
|
watch(showMarkName, (nV, oV) => {
|
if (nV != oV) {
|
new Promise((resolve, reject) => {
|
allMarkViews.forEach((m) => onChangeMarkLabel(m, nV));
|
resolve();
|
});
|
}
|
});
|
|
watch(
|
locationStatus,
|
(newVal, oldVal) => {
|
if (newVal?.dCode != oldVal?.dCode) {
|
districtSearch.removeDistrict();
|
districtSearch.drawDistrict(newVal.dCode);
|
} else if (newVal?.tCode != oldVal?.tCode) {
|
filterMarkViews(true);
|
}
|
},
|
{ immediate: true }
|
);
|
|
watch(scenetype, (nV, oV) => {
|
if (nV != oV) {
|
filterMarkViews(true);
|
sceneStatus.value = '0';
|
sceneStage.value = '0';
|
if (nV?.value == 1) {
|
showSceneStatus.value = true;
|
showSceneStage.value = true;
|
} else {
|
showSceneStatus.value = false;
|
showSceneStage.value = false;
|
}
|
}
|
});
|
|
watch(sceneStatus, (nV, oV) => {
|
if (nV != oV) {
|
filterMarkViews(true);
|
}
|
});
|
|
watch(sceneStage, (nV, oV) => {
|
if (nV != oV) {
|
filterMarkViews(true);
|
}
|
});
|
|
watch(filterText, (nV, oV) => {
|
if (nV != oV) {
|
filterMarkViews(true);
|
}
|
});
|
|
// 监听外部选中或移除场景变化
|
watch(
|
() => props.modelValue,
|
(nV, oV) => {
|
if (nV != oV) {
|
// 外部选中场景变化时,更新内部选中场景列表
|
selectedSceneList.value.forEach((ss) => {
|
const findOne = nV.some((s) => {
|
return s.guid == ss.guid;
|
});
|
if (!findOne) {
|
ss._checked = false;
|
}
|
});
|
selectedSceneList.value = nV;
|
// 外部选中场景变化时,更新地图标注内容
|
allMarkViews.forEach((mv) => {
|
const scene = mv.getExtData();
|
const findOne = selectedSceneList.value.some((s) => {
|
return s.guid == scene.guid;
|
});
|
if (findOne) {
|
changeSceneMark(mv, true);
|
} else {
|
changeSceneMark(mv, false);
|
}
|
});
|
}
|
}
|
);
|
|
function handleSceneCheck(scene, _mark) {
|
scene._checked = !scene._checked;
|
if (_mark == undefined) {
|
_mark = allMarkViews.find((mv) => {
|
const s = mv.getExtData();
|
return s.guid == scene.guid;
|
});
|
}
|
const index = selectedSceneList.value.findIndex((s) => s.guid == scene.guid);
|
if (index == -1) {
|
selectedSceneList.value.push(scene);
|
changeSceneMark(_mark, true);
|
} else {
|
selectedSceneList.value.splice(index, 1);
|
changeSceneMark(_mark, false);
|
}
|
emits('update:modelValue', selectedSceneList.value);
|
}
|
|
function handleVisibleChange(scene) {
|
const mv = markViewList.find((v) => {
|
return scene.guid == v.getExtData().guid;
|
});
|
scene._visible = !scene._visible;
|
if (scene._visible) {
|
map.add(mv);
|
} else {
|
map.remove(mv);
|
}
|
}
|
|
function locateTo(scene) {
|
const mv = markViewList.find((v) => {
|
return scene.guid == v.getExtData().guid;
|
});
|
if (mv) {
|
// mapUtil.setFitView(mv);
|
mapUtil.setCenter(mv.getPosition());
|
}
|
}
|
|
// 创建场景地图标注,添加鼠标事件和点击事件
|
function createSceneMarks() {
|
onMapMounted(() => {
|
allMarkViews = [];
|
props.data.forEach((d) => {
|
const findOne = selectedSceneList.value.some((s) => {
|
return s.guid == d.guid;
|
});
|
// 创建场景地图标注
|
const mark = marks.createMarker({
|
position: [d.longitude, d.latitude],
|
// img: sceneIcon(d.typeid),
|
content: markContentHtml(d.typeid, findOne),
|
label: d.index,
|
title: d.name,
|
extData: d
|
});
|
// 鼠标事件
|
// onMouseOverListener(mark);
|
onClickListener(mark);
|
|
allMarkViews.push(mark);
|
});
|
});
|
}
|
|
/**
|
* 筛选所选类型的场景
|
*/
|
var triggerTime = 0;
|
function filterMarkViews(setFitView) {
|
// 防抖处理
|
if (Date.now() - triggerTime < 500) {
|
return;
|
}
|
triggerTime = Date.now();
|
onMapMounted(() => {
|
if (markViewList.length > 0) {
|
map.remove(markViewList);
|
}
|
// 1. 筛选场景类型
|
if (scenetype.value == undefined) {
|
markViewList = allMarkViews;
|
} else {
|
markViewList = allMarkViews.filter((v) => {
|
const extData = v.getExtData();
|
return _filterScene(extData);
|
});
|
}
|
map.add(markViewList);
|
if (setFitView) {
|
// setTimeout(() => {
|
// map.setFitView(markViewList);
|
// }, 1000);
|
}
|
});
|
}
|
|
function onlineFormat(s) {
|
if (s == '0') {
|
return '下线';
|
} else {
|
return '上线';
|
}
|
}
|
|
/** 地图场景标记鼠标事件 *********************************************/
|
const onChangeMarkLabel = (mark, showMarkName) => {
|
const _extData = mark.getExtData();
|
let _name = _extData.name;
|
if (_name.length > 6) {
|
_name = _name.substring(0, 6) + '...';
|
}
|
mark.setLabel({
|
content: showMarkName ? _name : _extData.index
|
});
|
};
|
const onClickListener = (mark) => {
|
mark.on('click', (ev) => {
|
const _mark = ev.target;
|
const extData = _mark.getExtData();
|
handleSceneCheck(extData, _mark);
|
});
|
};
|
/********************************************************************/
|
|
/**
|
* 根据选中状态修改地图标记的样式
|
* @param {AMap.Marker} mark 地图标记
|
* @param checked
|
*/
|
function changeSceneMark(mark, checked) {
|
const scene = mark.getExtData();
|
if (checked) {
|
mark.setContent(markContentHtml(scene.typeid, true));
|
} else if (!checked) {
|
mark.setContent(markContentHtml(scene.typeid, false));
|
}
|
}
|
|
function ratioFormat(changeNum, proNum) {
|
if (proNum == 0) {
|
return '--';
|
}
|
return `${((changeNum / proNum) * 100).toFixed(0)}%`;
|
}
|
</script>
|
<style scoped>
|
.wrap {
|
position: absolute;
|
left: 0px;
|
top: 0;
|
width: 100%;
|
height: 100%;
|
}
|
.top-left-wrap {
|
/* position: absolute; */
|
left: 0px;
|
top: 1px;
|
width: 400px;
|
}
|
.bottom-left-wrap {
|
/* position: absolute; */
|
left: 0px;
|
bottom: 1px;
|
}
|
|
.card-left {
|
background-color: rgba(255, 255, 255, 0.8);
|
border-radius: 4px;
|
/* width: 350px; */
|
/* height: 50vh; */
|
box-shadow: var(--el-box-shadow);
|
z-index: 0;
|
/* padding: 8px; */
|
}
|
|
.scrollbar {
|
padding-right: 8px;
|
width: 400px;
|
}
|
|
.close-btn-right {
|
/* margin-left: -3px; */
|
height: 60px;
|
width: 20px;
|
}
|
|
.p-events-auto {
|
pointer-events: auto;
|
}
|
|
.p-events-none {
|
pointer-events: none;
|
}
|
|
.scene-item {
|
/* background-color: aliceblue; */
|
padding: 8px;
|
border-radius: 8px;
|
border: 1px solid var(--el-border-color);
|
box-shadow: var(--el-box-shadow-lighter);
|
margin-bottom: 6px;
|
}
|
|
.custom-content {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
</style>
|