Skip to content

Commit 4c98d2f

Browse files
fix: 优化路线功能
1 parent c198fa2 commit 4c98d2f

4 files changed

Lines changed: 295 additions & 22 deletions

File tree

src/App.vue

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ const {
3939
editorFormOpen,
4040
editorMode,
4141
editingLocationId,
42+
editingSegment,
43+
editSegment,
4244
exportCompleted,
4345
exportPendingLocationChanges,
4446
exportRoutes,
@@ -99,6 +101,8 @@ const {
99101
toggleCompleted,
100102
toggleDistrict,
101103
toggleFavorite,
104+
toggleRouteVisibility,
105+
toggleSegmentVisibility,
102106
toggleTeleportProtection,
103107
uploadImages,
104108
visibleCounts,
@@ -327,8 +331,8 @@ const {
327331
<input ref="routeImportInput" type="file" accept="application/json,.json" @change="importRoutes" />
328332
</div>
329333
<div class="route-list">
330-
<button v-for="route in routes" :key="route.id" type="button" :class="{ active: activeRouteId === route.id }" @click="activeRouteId = route.id">
331-
<span>{{ route.name }}</span><small>{{ route.segments.length }} 个路段</small>
334+
<button v-for="route in routes" :key="route.id" type="button" :class="{ active: activeRouteId === route.id, hidden: route.isHidden }" @click="toggleRouteVisibility(route)">
335+
<span>{{ route.name }}</span><small>{{ route.isHidden ? '已隐藏' : `${route.segments.length} 个路段` }}</small>
332336
</button>
333337
</div>
334338
<template v-if="activeRoute">
@@ -337,15 +341,16 @@ const {
337341
<button v-if="editorMode" type="button" @click="deleteRoute(activeRoute)">删除路线</button>
338342
</div>
339343
<div v-if="isAddingSegment" class="segment-editor">
340-
<span>依次点击已有标点或地图空白处:{{ segmentPoints.length }} </span>
344+
<span>{{ editingSegment ? `正在编辑:${editingSegment.name}` : '新路段' }}:{{ segmentPoints.length }} 个点</span>
341345
<button type="button" @click="segmentPoints = segmentPoints.slice(0, -1); renderRouteArrows()">撤销</button>
342346
<button type="button" @click="cancelSegment">取消</button>
343-
<button type="button" :disabled="segmentPoints.length < 2" @click="finishSegment">完成</button>
347+
<button type="button" :disabled="segmentPoints.length < 2" @click="finishSegment">{{ editingSegment ? '保存' : '完成' }}</button>
344348
</div>
345349
<button v-else-if="editorMode" class="add-segment-button" type="button" @click="startSegment">+ 添加路段</button>
346350
<div class="segment-list">
347-
<button v-for="segment in activeRoute.segments" :key="segment.id" type="button" @click="focusSegment(segment)">
348-
<span>{{ segment.name }}</span><small>{{ getSegmentPoints(segment).length }} 个点</small>
351+
<button v-for="segment in activeRoute.segments" :key="segment.id" type="button" :class="{ hidden: segment.isHidden }" @click="toggleSegmentVisibility(segment)">
352+
<span>{{ segment.name }}</span><small>{{ segment.isHidden ? '已隐藏' : `${getSegmentPoints(segment).length} 个点` }}</small>
353+
<i v-if="editorMode" @click.stop="editSegment(segment)">编辑</i>
349354
<i v-if="editorMode" @click.stop="deleteSegment(segment)">×</i>
350355
</button>
351356
</div>

src/composables/useMapApp.js

Lines changed: 171 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export function useMapApp() {
132132
const routePanelOpen = ref(false)
133133
const activeRouteId = ref(null)
134134
const isAddingSegment = ref(false)
135+
const editingSegmentId = ref(null)
135136
const segmentPoints = ref([])
136137
const routeImportInput = ref(null)
137138
const completedImportInput = ref(null)
@@ -192,6 +193,7 @@ export function useMapApp() {
192193

193194
// 统计和筛选派生数据:模板只消费这些计算结果。
194195
const activeRoute = computed(() => routes.value.find((route) => route.id === activeRouteId.value) || null)
196+
const editingSegment = computed(() => activeRoute.value?.segments.find((segment) => segment.id === editingSegmentId.value) || null)
195197
const getVisibleTypes = (location) => location.types.filter((type) => !categoryLookup.value[type]?.isHidden)
196198
const visibleLocationIds = computed(() => new Set(
197199
locations.value
@@ -659,17 +661,93 @@ export function useMapApp() {
659661
return Array.isArray(points) ? points.map(normalizeRoutePoint).filter(Boolean) : []
660662
}
661663

664+
function getRoutePointLabel(point, index) {
665+
const normalized = normalizeRoutePoint(point)
666+
if (!normalized) return `#${index + 1}`
667+
const location = normalized.locationId ? locationLookup.value[normalized.locationId] : null
668+
if (location) return `${index + 1}. ${location.name}`
669+
return `${index + 1}. ${normalized.lat.toFixed(2)}, ${normalized.lng.toFixed(2)}`
670+
}
671+
672+
function updateSegmentPoint(index, latlng) {
673+
const point = mapLatLngToWorld(latlng)
674+
segmentPoints.value = segmentPoints.value.map((item, pointIndex) => (
675+
pointIndex === index
676+
? { lat: Number(point.lat.toFixed(6)), lng: Number(point.lng.toFixed(6)) }
677+
: item
678+
))
679+
}
680+
681+
function createRoutePointPopup(index) {
682+
const container = document.createElement('div')
683+
container.className = 'route-point-popup'
684+
const title = document.createElement('b')
685+
title.textContent = getRoutePointLabel(segmentPoints.value[index], index)
686+
container.appendChild(title)
687+
688+
const actions = document.createElement('div')
689+
const actionItems = [
690+
['up', '上移', index === 0],
691+
['down', '下移', index === segmentPoints.value.length - 1],
692+
['delete', '删除', false],
693+
]
694+
actionItems.forEach(([action, label, disabled]) => {
695+
const button = document.createElement('button')
696+
button.type = 'button'
697+
button.textContent = label
698+
button.disabled = disabled
699+
button.addEventListener('click', (event) => {
700+
event.preventDefault()
701+
event.stopPropagation()
702+
if (action === 'up') moveSegmentPoint(index, -1)
703+
if (action === 'down') moveSegmentPoint(index, 1)
704+
if (action === 'delete') removeSegmentPoint(index)
705+
})
706+
actions.appendChild(button)
707+
})
708+
container.appendChild(actions)
709+
return container
710+
}
711+
712+
function drawEditableRoutePoint(point, index, color) {
713+
const marker = L.marker(worldToMapLatLng(point), {
714+
draggable: true,
715+
title: getRoutePointLabel(point, index),
716+
icon: L.divIcon({
717+
className: 'route-point-handle',
718+
html: `<i style="border-color:${color};background:${color}">${index + 1}</i>`,
719+
iconSize: [22, 22],
720+
iconAnchor: [11, 11],
721+
}),
722+
}).addTo(arrowLayer)
723+
724+
marker.bindPopup(createRoutePointPopup(index), {
725+
className: 'route-point-popup-shell',
726+
closeButton: false,
727+
offset: [0, -10],
728+
})
729+
marker.on('dragstart', () => marker.closePopup())
730+
marker.on('dragend', (event) => {
731+
updateSegmentPoint(index, event.target.getLatLng())
732+
renderRouteArrows()
733+
})
734+
}
735+
662736
function drawRoutePath(points, color, temporary = false) {
663737
points.forEach((point, index) => {
664-
L.circleMarker(worldToMapLatLng(point), {
665-
className: 'route-point',
666-
color,
667-
fillColor: color,
668-
fillOpacity: temporary ? 0.72 : 0.9,
669-
opacity: 1,
670-
radius: point.locationId ? 4 : 5,
671-
weight: 2,
672-
}).addTo(arrowLayer)
738+
if (temporary) {
739+
drawEditableRoutePoint(point, index, color)
740+
} else {
741+
L.circleMarker(worldToMapLatLng(point), {
742+
className: 'route-point',
743+
color,
744+
fillColor: color,
745+
fillOpacity: 0.9,
746+
opacity: 1,
747+
radius: point.locationId ? 4 : 5,
748+
weight: 2,
749+
}).addTo(arrowLayer)
750+
}
673751
if (index > 0) drawArrow(points[index - 1], point, color, temporary)
674752
})
675753
}
@@ -678,9 +756,11 @@ export function useMapApp() {
678756
return importedRoutes.filter((route) => route && typeof route === 'object').map((route, routeIndex) => ({
679757
id: String(route.id || `route-${Date.now()}-${routeIndex}`),
680758
name: String(route.name || `路线 ${routeIndex + 1}`),
759+
isHidden: route.isHidden === true,
681760
segments: Array.isArray(route.segments) ? route.segments.filter((segment) => segment && typeof segment === 'object').map((segment, segmentIndex) => ({
682761
id: String(segment.id || `segment-${Date.now()}-${routeIndex}-${segmentIndex}`),
683762
name: String(segment.name || `路段 ${segmentIndex + 1}`),
763+
isHidden: segment.isHidden === true,
684764
points: getSegmentPoints(segment),
685765
})) : [],
686766
}))
@@ -722,7 +802,15 @@ export function useMapApp() {
722802
return
723803
}
724804
const colors = ['#ffd27d', '#8adfd6', '#e8a6ff', '#ff8a70', '#87a9ff']
725-
activeRoute.value?.segments.forEach((segment, index) => drawRoutePath(getSegmentPoints(segment), colors[index % colors.length]))
805+
routes.value
806+
.filter((route) => !route.isHidden)
807+
.forEach((route, routeIndex) => {
808+
route.segments
809+
.filter((segment) => !segment.isHidden)
810+
.forEach((segment, segmentIndex) => {
811+
drawRoutePath(getSegmentPoints(segment), colors[(routeIndex + segmentIndex) % colors.length])
812+
})
813+
})
726814
}
727815

728816
// 实时导航:维护 WebSocket 连接、箭头角度和地图跟随。
@@ -1336,18 +1424,52 @@ export function useMapApp() {
13361424
function startSegment() {
13371425
if (!activeRoute.value) return
13381426
isAddingSegment.value = true
1427+
editingSegmentId.value = null
13391428
segmentPoints.value = []
13401429
selectedLocation.value = null
13411430
}
13421431

1432+
function editSegment(segment) {
1433+
if (!activeRoute.value || !segment) return
1434+
isAddingSegment.value = true
1435+
editingSegmentId.value = segment.id
1436+
segmentPoints.value = getSegmentPoints(segment)
1437+
selectedLocation.value = null
1438+
renderRouteArrows()
1439+
}
1440+
13431441
function cancelSegment() {
13441442
isAddingSegment.value = false
1443+
editingSegmentId.value = null
13451444
segmentPoints.value = []
13461445
renderRouteArrows()
13471446
}
13481447

1448+
function removeSegmentPoint(index) {
1449+
segmentPoints.value = segmentPoints.value.filter((_, pointIndex) => pointIndex !== index)
1450+
renderRouteArrows()
1451+
}
1452+
1453+
function moveSegmentPoint(index, offset) {
1454+
const targetIndex = index + offset
1455+
if (targetIndex < 0 || targetIndex >= segmentPoints.value.length) return
1456+
const nextPoints = [...segmentPoints.value]
1457+
;[nextPoints[index], nextPoints[targetIndex]] = [nextPoints[targetIndex], nextPoints[index]]
1458+
segmentPoints.value = nextPoints
1459+
renderRouteArrows()
1460+
}
1461+
13491462
async function finishSegment() {
13501463
if (!activeRoute.value || segmentPoints.value.length < 2) return
1464+
if (editingSegment.value) {
1465+
editingSegment.value.points = [...segmentPoints.value]
1466+
isAddingSegment.value = false
1467+
editingSegmentId.value = null
1468+
segmentPoints.value = []
1469+
await persistMapData()
1470+
renderRouteArrows()
1471+
return
1472+
}
13511473
const name = window.prompt('路段名称')
13521474
if (!name?.trim()) return
13531475
activeRoute.value.segments.push({
@@ -1356,6 +1478,7 @@ export function useMapApp() {
13561478
points: [...segmentPoints.value],
13571479
})
13581480
isAddingSegment.value = false
1481+
editingSegmentId.value = null
13591482
segmentPoints.value = []
13601483
await persistMapData()
13611484
renderRouteArrows()
@@ -1364,6 +1487,28 @@ export function useMapApp() {
13641487
async function deleteSegment(segment) {
13651488
if (!activeRoute.value || !window.confirm(`删除路段“${segment.name}”?`)) return
13661489
activeRoute.value.segments = activeRoute.value.segments.filter((item) => item.id !== segment.id)
1490+
if (editingSegmentId.value === segment.id) cancelSegment()
1491+
await persistMapData()
1492+
renderRouteArrows()
1493+
}
1494+
1495+
async function toggleRouteVisibility(route) {
1496+
if (activeRouteId.value !== route.id) {
1497+
activeRouteId.value = route.id
1498+
if (route.isHidden) {
1499+
route.isHidden = false
1500+
await persistMapData()
1501+
renderRouteArrows()
1502+
}
1503+
return
1504+
}
1505+
route.isHidden = !route.isHidden
1506+
await persistMapData()
1507+
renderRouteArrows()
1508+
}
1509+
1510+
async function toggleSegmentVisibility(segment) {
1511+
segment.isHidden = !segment.isHidden
13671512
await persistMapData()
13681513
renderRouteArrows()
13691514
}
@@ -1400,7 +1545,14 @@ export function useMapApp() {
14001545
fitLocationsBounds(focusLocations.length ? focusLocations : filteredLocations.value)
14011546
}, { deep: true })
14021547
watch(activeDistricts, persistMarkerFilters, { deep: true })
1403-
watch(activeRouteId, () => nextTick(renderRouteArrows))
1548+
watch(activeRouteId, () => {
1549+
if (isAddingSegment.value) {
1550+
isAddingSegment.value = false
1551+
editingSegmentId.value = null
1552+
segmentPoints.value = []
1553+
}
1554+
nextTick(renderRouteArrows)
1555+
})
14041556
watch([() => [...activeCategories.value], keepTeleportEnabled, showIncompleteOnly, showFavoritesOnly], persistMarkerFilters)
14051557
watch(editorMode, () => {
14061558
if (!editorMode.value) showPendingLocationChangesOnly.value = false
@@ -1518,13 +1670,17 @@ export function useMapApp() {
15181670
editorFormOpen,
15191671
editorMode,
15201672
editingLocationId,
1673+
editingSegment,
1674+
editSegment,
15211675
exportCompleted,
15221676
exportPendingLocationChanges,
15231677
exportRoutes,
15241678
favoriteCount,
15251679
favoriteIds,
15261680
filteredLocations,
15271681
finishSegment,
1682+
focusSegment,
1683+
getRoutePointLabel,
15281684
getSegmentPoints,
15291685
getVisibleTypes,
15301686
groupedCategories,
@@ -1542,6 +1698,7 @@ export function useMapApp() {
15421698
locationForm,
15431699
mapElement,
15441700
mergeAdjacentLocationsEnabled,
1701+
moveSegmentPoint,
15451702
navigationConnectionLabel,
15461703
navigationConnectionStatus,
15471704
navigationHost,
@@ -1557,6 +1714,7 @@ export function useMapApp() {
15571714
query,
15581715
realtimeNavigationEnabled,
15591716
renderRouteArrows,
1717+
removeSegmentPoint,
15601718
resetView,
15611719
routeImportInput,
15621720
routePanelOpen,
@@ -1578,6 +1736,8 @@ export function useMapApp() {
15781736
toggleCompleted,
15791737
toggleDistrict,
15801738
toggleFavorite,
1739+
toggleRouteVisibility,
1740+
toggleSegmentVisibility,
15811741
toggleTeleportProtection,
15821742
uploadImages,
15831743
visibleCounts,

0 commit comments

Comments
 (0)