@@ -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