diff --git a/frontend/src/features/plans/RouteMap.test.tsx b/frontend/src/features/plans/RouteMap.test.tsx index e02aa3a..0292a1a 100644 --- a/frontend/src/features/plans/RouteMap.test.tsx +++ b/frontend/src/features/plans/RouteMap.test.tsx @@ -116,15 +116,28 @@ it("uses the same compact order number and full hover label on the fallback map" providerName="amap" start={{ label: "家", position: { latitude: 22.54, longitude: 114.05 } }} markers={markers} - route={null} + route={{ + task_ids: [7], + distance_meters: 1000, + duration_seconds: 600, + polyline: [ + { latitude: 22.54, longitude: 114.05 }, + { latitude: 22.55, longitude: 114.06 }, + ], + }} selectedTaskId={null} onSelectTask={onSelectTask} />, ); const marker = screen.getByRole("button", { name: "订单 #12,虚构定位客户,路线第 1 站" }); + const homeMarker = screen.getByTitle("家(起点与终点)"); + const routeLine = screen.getByRole("img", { name: "按真实坐标绘制的路线" }).querySelector("polyline"); expect(marker).toHaveTextContent("#12"); expect(marker).toHaveAttribute("title", "订单 #12 · 虚构定位客户 · 路线第 1 站"); + expect(homeMarker).toHaveClass("bg-emerald-600"); + expect(homeMarker).not.toHaveClass("bg-slate-900"); + expect(routeLine).toHaveAttribute("stroke", "#f97316"); expect(screen.queryByText("虚构定位客户")).not.toBeInTheDocument(); fireEvent.click(marker); expect(onSelectTask).toHaveBeenCalledWith(7); diff --git a/frontend/src/features/plans/RouteMap.tsx b/frontend/src/features/plans/RouteMap.tsx index 09e6eb9..fa3a652 100644 --- a/frontend/src/features/plans/RouteMap.tsx +++ b/frontend/src/features/plans/RouteMap.tsx @@ -2,6 +2,8 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { AmapMapProvider, + HOME_MARKER_COLOR_CLASS, + ROUTE_LINE_COLOR, hasAmapBrowserKey, hasAmapBrowserSecurityCode, markerDisplayOffset, @@ -90,11 +92,11 @@ function CoordinateCanvas({ - {polyline ? : null} + {polyline ? : null} {start ? (() => { const projected = project(start.position); - return 家 · 起终点; + return 家 · 起终点; })() : null} {markers.map((marker, index) => { const projected = project(marker.position); diff --git a/frontend/src/features/plans/mapProvider.test.ts b/frontend/src/features/plans/mapProvider.test.ts index 44a8c11..9551117 100644 --- a/frontend/src/features/plans/mapProvider.test.ts +++ b/frontend/src/features/plans/mapProvider.test.ts @@ -77,6 +77,39 @@ afterEach(() => { delete window.AMap; }); +it("uses a warm route line and a green home marker", async () => { + const provider = new AmapMapProvider(); + const controller = await provider.mount(document.createElement("div"), { + start: { label: "家", position: { latitude: 22.54, longitude: 114.05 } }, + markers: [], + polyline: [ + { latitude: 22.54, longitude: 114.05 }, + { latitude: 22.55, longitude: 114.06 }, + ], + selectedTaskId: null, + locationEditing: false, + draftPosition: null, + }, { + onSelectTask: vi.fn(), + }); + + const homeMarker = markerInstances.find((marker) => marker.options.title === "家"); + const homeContent = homeMarker?.options.content as HTMLDivElement; + expect(homeContent.className).toContain("bg-emerald-600"); + expect(homeContent.className).not.toContain("bg-slate-900"); + + const routeLine = markerInstances.find((marker) => marker.options.strokeColor); + expect(routeLine?.options).toMatchObject({ + strokeColor: "#f97316", + strokeOpacity: 0.9, + isOutline: true, + outlineColor: "#fff7ed", + showDir: false, + }); + + controller.dispose(); +}); + it("places a GCJ-02 draft pin on map click and updates it after dragging", async () => { const onDraftPositionChange = vi.fn(); const provider = new AmapMapProvider(); diff --git a/frontend/src/features/plans/mapProvider.ts b/frontend/src/features/plans/mapProvider.ts index 82d23f4..be158e7 100644 --- a/frontend/src/features/plans/mapProvider.ts +++ b/frontend/src/features/plans/mapProvider.ts @@ -9,6 +9,9 @@ export interface MapRenderModel { draftPosition?: PlanGeoPoint | null; } +export const ROUTE_LINE_COLOR = "#f97316"; +export const HOME_MARKER_COLOR_CLASS = "bg-emerald-600"; + export interface MapInteractionCallbacks { onSelectTask: (taskId: number) => void; onDraftPositionChange?: (position: PlanGeoPoint) => void; @@ -128,18 +131,15 @@ function position(point: PlanGeoPoint): [number, number] { return [point.longitude, point.latitude]; } -function markerContent( +function homeMarkerContent( label: string, name: string | null = null, - selected = false, - offset: MarkerDisplayOffset = { x: 0, y: 0 }, - muted = false, ): HTMLDivElement { const content = document.createElement("div"); content.className = [ - "flex items-center gap-1 rounded-full border-2 border-white px-2 py-1", + "flex items-center gap-1 rounded-full border-2 border-emerald-100 px-2 py-1", "max-w-36 text-xs font-bold whitespace-nowrap text-white shadow-md", - selected ? "bg-amber-600 ring-2 ring-amber-300" : muted ? "bg-slate-400" : "bg-slate-900", + HOME_MARKER_COLOR_CLASS, ].join(" "); const sequence = document.createElement("span"); sequence.textContent = label; @@ -150,7 +150,6 @@ function markerContent( customerName.textContent = name; content.append(customerName); } - content.style.transform = `translate(${offset.x}px, ${offset.y}px)`; return content; } @@ -273,7 +272,7 @@ export class AmapMapProvider implements MapProvider { position: position(model.start.position), title: model.start.label, anchor: "center", - content: markerContent("家", "起终点"), + content: homeMarkerContent("家", "起终点"), }), ); } @@ -302,11 +301,15 @@ export class AmapMapProvider implements MapProvider { overlays.push( new AMap.Polyline({ path: model.polyline.map(position), - strokeColor: "#0f172a", + strokeColor: ROUTE_LINE_COLOR, strokeWeight: 5, - strokeOpacity: 0.82, + strokeOpacity: 0.9, + isOutline: true, + outlineColor: "#fff7ed", + borderWeight: 2, lineJoin: "round", - showDir: true, + lineCap: "round", + showDir: false, }), ); }