Files
estate/src/views/system/inspection/Route/addInspectionRoute.vue
2026-06-22 08:14:35 +08:00

494 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="AddBuildingBox">
<PageHeader></PageHeader>
<div class="AddBuildingBody">
<div class="title">基本信息</div>
<div class="addBuildingFormBody">
<el-form class="formBody" label-position="right" ref="formRef" :model="form" :rules="rules" label-width="130px">
<el-form-item label="巡检路线名称" prop="routeName">
<el-input v-model.trim="form.routeName" placeholder="请输入巡检路线名称" />
</el-form-item>
<!-- <el-form-item label="巡检路线描述" prop="description">
<el-input type="textarea" :rows="3" v-model.trim="form.description" placeholder="请输入巡检路线描述" />
</el-form-item> -->
<!-- <el-form-item label="预计巡检时长" prop="estimatedTime">
<el-input v-model.trim.number="form.estimatedTime" placeholder="预计巡检时长">
<template #append>分钟</template>
</el-input>
</el-form-item> -->
<!-- <el-form-item label="巡检路线状态" prop="routeStatus">
<el-radio-group v-model="form.routeStatus">
<el-radio v-for="(item, index) in com_inspection_route_status" :key="index" :value="item.value" :label="item.label"></el-radio>
</el-radio-group>
</el-form-item> -->
<el-form-item label="巡检点">
<div class="itembox">
<div
class="item"
:class="{
active: checkoutPointlist.includes(item.id)
}"
@click="checkoutItem(item.id)"
v-for="(item, index) in pointslist"
:key="index"
>
<el-icon v-show="checkoutPointlist.includes(item.id)"><Check /></el-icon>
<div>{{ item.pointName }}</div>
</div>
</div>
</el-form-item>
<el-form-item label="绘制巡检路线">
<div class="map" id="containerMap"></div>
<div class="tips flex flex-items-center justify-between">
<div>点击地图绘制巡检路线再次点击可继续绘制下一条但只能保留一条</div>
<div>
<span class="deleteMarker" @click="clearAll">清空所有路线</span>
</div>
</div>
</el-form-item>
<el-form-item style="margin-top: 30px">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="cancelForm">返回</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import PageHeader from '@/components/Pageheader/index.vue';
import { listPoint } from '@/api/system/InspectionPoint';
import { onMounted, onUnmounted } from 'vue';
import AMapLoader from '@amap/amap-jsapi-loader';
import { addRoute, getRoute, updateRoute } from '@/api/system/InspectionRoute';
import { getVillageByIdAPI } from '@/api/system/community';
import { Check } from '@element-plus/icons-vue';
const router = useRouter();
const route = useRoute();
const formRef = ref();
const form = ref({
id: null,
startLat: '',
startLng: '',
endLat: '',
endLng: '',
routeName: '', // 巡检路线名称
description: '', // 巡检路线描述
routeStatus: '0', // 巡检路线状态
estimatedTime: null, // 预计巡检时长
pathPoints: '', // 巡检路线经纬度
totalPoints: '' // 巡检点
});
const rules = ref({
routeName: [{ required: true, message: '请输入巡检路线名称', trigger: 'blur' }],
estimatedTime: [{ required: true, message: '请输入预计巡检时长', trigger: 'blur' }]
});
// !== 初始化 =============================================================================
const routeid = ref(null);
// 巡检点 列表
const pointslist = ref([]);
// 选择巡检点列表
const checkoutPointlist = ref([]);
function checkoutItem(id: string) {
if (checkoutPointlist.value.includes(id)) {
checkoutPointlist.value = checkoutPointlist.value.filter((item) => item !== id);
removePointMarker(id);
} else {
checkoutPointlist.value.push(id);
const find = pointslist.value.find((item) => item.id === id);
if (find && find.location) {
if (find && find.location) {
addPointMarker(id, find.location, find.pointName);
}
}
}
// 调整地图视野以适应所有 marker
map.value.setFitView();
}
// 添加 marker
function addPointMarker(id: string, location: string, title?: string) {
if (!map.value || !AMapLib.value) return;
const [lng, lat] = location.split(',').map((v) => Number(v.trim()));
if (!Number.isFinite(lng) || !Number.isFinite(lat)) return;
// 获取到经纬度后,添加 marker 数组
const lnglat = new AMapLib.value.LngLat(lng, lat);
const index = Object.keys(markers.value).length + 1; // 获取当前 marker 数量,作为新的 marker 的编号
markers.value[id] = new AMapLib.value.Marker({
position: lnglat,
map: map.value,
title: title || '巡检点',
label: title || '巡检点',
content: `
<div style="display:flex;align-items:center;justify-content:center;width:30px;height:30px;line-height:18px;border-radius:50%;background-color:rgba(26,117,255,0.3);">
<div
style=" width: 20px;
border-radius: 50%;
height: 20px;
line-height: 20px;
background-color: rgba(26,117,255,1);
color: #ffffff;
font-size: 20px;
text-align: center;"
>${index}</div>
</div>`
});
}
// 移除 marker
function removePointMarker(id: string) {
const markerItem = markers.value[id];
if (markerItem) {
map.value.remove(markerItem);
delete markers.value[id];
}
}
// ! 初始化 =========================================================================
function init() {
if (route.query.id) {
routeid.value = route.query.id;
// 获取巡检点详情
getRoute(routeid.value).then((res) => {
form.value = {
id: res.data.id,
startLat: res.data.startLat,
startLng: res.data.startLng,
endLat: res.data.endLat,
endLng: res.data.endLng,
routeName: res.data.routeName, // 巡检路线名称
description: res.data.description, // 巡检路线描述
routeStatus: res.data.routeStatus, // 巡检路线状态
estimatedTime: res.data.estimatedTime, // 预计巡检时长
pathPoints: res.data.pathPoints, // 巡检路线经纬度
totalPoints: res.data.totalPoints // 巡检点
};
nextTick(() => {
if (routeid.value) {
checkoutPointlist.value = res.data.totalPoints ? res.data.totalPoints.split(',') : [];
if (checkoutPointlist.value.length > 0) {
// 回显 marker
checkoutPointlist.value.forEach((id) => {
const find = res.data.points.find((item) => item.id === id);
if (find && find.location) {
addPointMarker(id, find.location, find.pointName);
}
});
}
// 回显路线
if (res.data.pathPoints) {
renderSavedPath(res.data.pathPoints);
}
}
});
});
}
}
// ! map
const mapReady = ref(false);
const AMapLib = ref(null);
const map = ref<any>(null);
const markers = ref<Record<string, any>>({});
const mouseTool = ref(null);
const drawnPolylines = ref([]); // 存所有线条
const villageid = localStorage.getItem('villageid');
const aloaderkey = import.meta.env.VITE_GCJ02_KEY_GD;
const VITE_GCJ02_JS_CODE_GD = import.meta.env.VITE_GCJ02_JS_CODE_GD;
onMounted(async () => {
// 获取巡检点列表
if (!routeid.value) {
listPoint({}).then((res) => {
pointslist.value = res.rows;
});
}
await nextTick();
window._AMapSecurityConfig = { securityJsCode: VITE_GCJ02_JS_CODE_GD };
AMapLoader.load({
key: aloaderkey,
version: '2.0',
plugins: ['AMap.Scale', 'AMap.Marker']
}).then(async (AMap) => {
AMapLib.value = AMap;
map.value = new AMap.Map('containerMap', {
viewMode: '3D',
zoom: 11
});
const villageinfo = await getVillageByIdAPI(villageid);
AMap.plugin('AMap.Geocoder', function () {
const city = villageinfo.data.city.replaceAll('/', '');
const geocoder = new AMap.Geocoder({
city: city // city 指定进行编码查询的城市支持传入城市名、adcode 和 citycode
});
geocoder.getLocation(city, function (status, result) {
if (status === 'complete' && result.info === 'OK') {
// result中对应详细地理坐标信息
map.value.setCenter(result.geocodes[0].location.toArray());
}
});
});
mapReady.value = true;
// 初始化绘图插件 MouseTool
AMap.plugin(['AMap.MouseTool'], () => {
mouseTool.value = new AMap.MouseTool(map.value);
// ✅ 只在这里调用一次,不要重复调用
// 监听绘制(全局只绑定一次!)
mouseTool.value.on('draw', (e) => {
if (!e.obj) return;
// ==========================================
// 核心:永远只保留一条线,画新的自动删旧的
// ==========================================
if (drawnPolylines.value.length > 0) {
map.value.remove(drawnPolylines.value[0]);
}
drawnPolylines.value = [e.obj];
// 画完继续允许画下一条(但永远只保留一条)
initDrawTool();
});
initDrawTool();
});
init();
});
});
// 回显
function renderSavedPath(pathStr: string) {
if (!pathStr || !map.value || !AMapLib.value) return;
try {
const path = JSON.parse(pathStr);
if (!Array.isArray(path) || path.length === 0) return;
// 先清空旧路线
drawnPolylines.value.forEach((line) => {
map.value.remove(line);
});
drawnPolylines.value = []; // 🔴 清空数组
// 只回显一条
const polyline = new AMapLib.value.Polyline({
path: path,
strokeColor: '#3366FF',
strokeOpacity: 1,
strokeWeight: 3,
strokeStyle: 'solid',
map: map.value
});
drawnPolylines.value.push(polyline); // ✅ 只一条
map.value.setFitView(polyline);
} catch (e) {
console.error('路线解析失败', e);
}
}
// 初始化绘图(只执行一次,防止事件重复绑定)
function initDrawTool() {
if (!mouseTool.value) return;
// 先关闭可能存在的绘图
mouseTool.value.close(false);
// 开启折线绘制
mouseTool.value.polyline({
strokeColor: '#3366FF',
strokeOpacity: 1,
strokeWeight: 3,
strokeStyle: 'solid'
});
}
// 清空所有画线
function clearAll() {
if (mouseTool.value) {
mouseTool.value.close(true); // 关闭绘图并清空
drawnPolylines.value.forEach((line) => {
map.value.removeOverlay(line);
});
drawnPolylines.value = [];
initDrawTool();
}
}
// !!! 核心:获取绘制的路线经纬度数组
function getPolylinePath() {
if (drawnPolylines.value.length === 0) return [];
// 获取最后一条线
const lastPolyline = drawnPolylines.value[drawnPolylines.value.length - 1];
// const lastLine = drawnPolylines.value.pop();
const path = lastPolyline.getPath().map((p) => [p.lng, p.lat]);
return path;
}
/**
* 修复高德地图 Polyline 闭合路线最后一段不显示
* 解决:最后两点坐标相同 → 长度0 → 不渲染
*/
function fixClosePath(path: number[][]): number[][] {
if (!path || path.length < 2) return path;
const newPath = [...path];
// 如果是闭合路线(最后一点 ≈ 第一点)
const first = newPath[0];
const last = newPath[newPath.length - 1];
// 判断是否几乎重合
const isSame = Math.abs(last[0] - first[0]) < 1e-8 && Math.abs(last[1] - first[1]) < 1e-8;
if (isSame) {
// 【关键】最后一点极微小偏移,强制生成线段
newPath[newPath.length - 1] = [last[0] + 0.0000001, last[1] + 0.0000001];
}
return newPath;
}
// !== 提交 =============================================================================
const submitForm = () => {
formRef.value?.validate(async (valid: boolean) => {
// if (valid) {
// 获取路线经纬度
const path = getPolylinePath() as [number, number][];
if (path.length === 0) {
ElMessage.warning('请绘制巡检路线');
return;
}
// 转成字符串传给后端
const [start_lng, start_lat] = path.at(0) as [number, number];
const [end_lng, end_lat] = path.at(-1) as [number, number];
form.value.startLng = `${start_lng}`;
form.value.startLat = `${start_lat}`;
form.value.endLng = `${end_lng}`;
form.value.endLat = `${end_lat}`;
form.value.pathPoints = JSON.stringify(path);
form.value.totalPoints = checkoutPointlist.value.join(',');
if (routeid.value) {
updateRoute(form.value).then(() => {
ElMessage.success('编辑成功');
cancelForm();
});
} else {
addRoute(form.value).then(() => {
ElMessage.success('添加成功');
cancelForm();
});
}
// }
});
};
// 返回
const cancelForm = () => {
router.back();
};
onUnmounted(() => {
map.value?.destroy();
});
</script>
<style scoped lang="scss">
.AddBuildingBox {
padding: 15px;
height: 100%;
width: 100%;
}
.AddBuildingBody {
margin-top: 20px;
display: flex;
flex-direction: column;
justify-content: center;
background-color: white;
padding: 15px;
.title {
height: 40px;
line-height: 40px;
padding-left: 20px;
background-color: rgba(255, 255, 255, 1);
color: rgba(16, 16, 16, 1);
border-bottom: 1px solid #bbbbbb;
margin-bottom: 60px;
}
.formBody {
width: 800px;
margin: 0 auto;
.itembox {
display: flex;
flex-wrap: wrap;
.item {
display: flex;
align-items: center;
margin-right: 15px;
margin-bottom: 10px;
font-size: 14px;
color: #606266;
border: 1px solid #ccc;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
&:hover {
border-color: #409eff;
color: #409eff;
}
div {
margin-left: 10px;
}
&.active {
border-color: #409eff;
color: #409eff;
background-color: #f0f9ff;
}
}
}
.map {
width: 100%;
min-height: 500px;
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 4px;
margin-top: 10px;
}
.tips {
width: 100%;
margin-top: 10px;
font-size: 12px;
color: #ccc;
.deleteMarker {
margin-left: 20px;
cursor: pointer;
color: #f00;
}
}
&:deep(.el-form-item__content, .el-select, .el-input) {
width: 350px !important;
margin-right: 10px;
.el-cascader {
width: 100%;
}
}
.el-form-item {
margin-bottom: 20px;
align-items: center;
}
.el-button {
width: 80px;
height: 35px;
margin-right: 20px;
}
}
}
</style>