@heycar/heycars-map 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .n8tgem0{pointer-events:none;position:absolute;bottom:50%;left:0;right:0;margin-left:auto;margin-right:auto;display:flex;flex-direction:column;align-items:center;z-index:1}.n8tgem1{pointer-events:auto;padding:2.67vw 4.27vw;background:linear-gradient(90deg,#79AFFF 0%,#5C8DFF 32%,#507FFF 75%,#4471FF 100%);border-radius:3.2vw;display:flex;align-items:center}.n8tgem2{padding-right:2.67vw;max-width:41.33vw}.n8tgem3{display:-webkit-box;font-size:3.47vw;font-family:PingFangSC-Semibold,PingFang SC;font-weight:600;color:#fff;line-height:4.8vw;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.n8tgem4{margin-top:.53vw;font-size:2.93vw;font-family:PingFangSC-Regular,PingFang SC;font-weight:400;color:#fff;line-height:4.27vw;opacity:.8}.n8tgem5{width:1.758vw;height:2.93vw}.n8tgem6{width:.8vw;height:4vw;background:#4C80FF;border-radius:0vw 0vw 26.67vw 26.67vw}.n8tgem7{width:6.9355vw;height:10.67vw}.rgf09w0{width:7.1045vw;height:10.93vw;display:flex}._15rq2xn0{display:flex;flex-direction:column;align-items:center;margin-bottom:-1.6vw}._15rq2xn1{padding:2.13vw 4.5vw 2.67vw 4.27vw;display:flex;align-items:center;background:linear-gradient(90deg,#79AFFF 0%,#5C8DFF 32%,#507FFF 75%,#4471FF 100%);border-radius:3.2vw}._15rq2xn2{font-size:3.47vw;font-family:PingFangSC-Semibold,PingFang SC;font-weight:600;color:#fff;line-height:4.8vw;white-space:nowrap}._15rq2xn3{font-size:2.93vw;font-family:PingFangSC-Regular,PingFang SC;font-weight:400;color:#eafaf8;line-height:4.27vw;white-space:nowrap;opacity:.8}._15rq2xn4{margin-left:3.2vw;width:1.44vw;height:2.4vw}._15rq2xn5{width:.8vw;height:4vw;background:#4C80FF;border-radius:0vw 0vw 26.67vw 26.67vw;z-index:1}._15rq2xn6{margin-top:-1.5vw;width:3.2vw;height:3.2vw}._1d8ur7t0,._7anfuo0{position:relative}._4a4ovk0{margin-bottom:-.4vw;display:flex;flex-direction:column;align-items:center}._4a4ovk1{padding:1.07vw 2.4vw 1.6vw 2.67vw;background:#FFFFFF;box-shadow:0 .53vw 2.4vw #3f3f3f33;border-radius:2.13vw}._4a4ovk2{padding:2.67vw 2.67vw 2.4vw 3.2vw;background:#FFFFFF;box-shadow:0 0 2.67vw #4e556c26;border-radius:4.8vw;font-size:3.2vw;font-family:PingFangSC-Medium,PingFang SC;font-weight:500;color:#1e1e1e;line-height:4.53vw;white-space:nowrap}._4a4ovk3{font-size:3.2vw;font-family:PingFangSC-Medium,PingFang SC;font-weight:500;color:#1e1e1e;line-height:4.53vw;white-space:nowrap}._4a4ovk4{font-size:2.4vw;font-family:PingFangSC-Regular,PingFang SC;font-weight:400;color:#7e7e7e;line-height:3.47vw;white-space:nowrap}._4a4ovk5{color:#4471ff;font-family:PingFangSC-Medium,PingFang SC;font-weight:500}._4a4ovk6{margin-top:1.33vw;width:6.192vw;height:7.998vw}._4a4ovk7{margin-top:2.4vw;width:6.192vw;height:7.998vw}._1l6offo0{display:flex}._1l6offo1{width:4.53vw;height:4.53vw;margin-bottom:-2.265vw}._1l6offo2{width:16.27vw;height:16.27vw;margin-bottom:-8.135vw}.fhyw80{display:flex;align-items:center}.fhyw81{flex-direction:row-reverse}.fhyw83{margin-right:-2.265vw}.fhyw84{margin-left:-2.265vw}.fhyw85{margin-bottom:-2.265vw}.fhyw86{margin-right:calc(50% - 2.265vw)}.fhyw87{margin-left:calc(50% - 2.265vw)}.fhyw88{display:flex;width:4.53vw;height:4.53vw}.fhyw89{opacity:0}.fhyw8b{font-size:3.47vw;font-family:PingFangSC-Semibold,PingFang SC;font-weight:600;color:#243e66;line-height:4.53vw;white-space:nowrap}._65j3sr1{margin-top:1.87vw;margin-bottom:-5.5vw;width:6vw;height:11vw}@keyframes rrvp0y0{0%{transform:scale(0);opacity:1}33.33%{transform:scale(.5);opacity:1}66.66%{transform:scale(1);opacity:0}to{transform:scale(1);opacity:0}}@keyframes rrvp0y1{0%{transform:scale(0);opacity:0}33.33%{transform:scale(0);opacity:1}66.66%{transform:scale(.5);opacity:1}to{transform:scale(1);opacity:0}}.rrvp0y2{display:flex;margin-bottom:-30.935vw;width:61.87vw;height:61.87vw;animation-name:rrvp0y0;animation-duration:3s;animation-iteration-count:infinite;animation-timing-function:linear}.rrvp0y3{animation-name:rrvp0y1}
1
+ .n8tgem0{pointer-events:none;position:absolute;bottom:50%;left:0;right:0;margin-left:auto;margin-right:auto;display:flex;flex-direction:column;align-items:center;z-index:1}.n8tgem1{pointer-events:auto;padding:2.67vw 4.27vw;background:linear-gradient(90deg,#79AFFF 0%,#5C8DFF 32%,#507FFF 75%,#4471FF 100%);border-radius:3.2vw;display:flex;align-items:center}.n8tgem2{padding-right:2.67vw;max-width:41.33vw}.n8tgem3{display:-webkit-box;font-size:3.47vw;font-family:PingFangSC-Semibold,PingFang SC;font-weight:600;color:#fff;line-height:4.8vw;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.n8tgem4{margin-top:.53vw;font-size:2.93vw;font-family:PingFangSC-Regular,PingFang SC;font-weight:400;color:#fff;line-height:4.27vw;opacity:.8}.n8tgem5{width:1.758vw;height:2.93vw}.n8tgem6{width:.8vw;height:4vw;background:#4C80FF;border-radius:0vw 0vw 26.67vw 26.67vw}.n8tgem7{width:6.9355vw;height:10.67vw}.rgf09w0{width:7.1045vw;height:10.93vw;display:flex}._15rq2xn0{display:flex;flex-direction:column;align-items:center;margin-bottom:-1.6vw}._15rq2xn1{padding:2.13vw 4.5vw 2.67vw 4.27vw;display:flex;align-items:center;background:linear-gradient(90deg,#79AFFF 0%,#5C8DFF 32%,#507FFF 75%,#4471FF 100%);border-radius:3.2vw}._15rq2xn2{font-size:3.47vw;font-family:PingFangSC-Semibold,PingFang SC;font-weight:600;color:#fff;line-height:4.8vw;white-space:nowrap}._15rq2xn3{font-size:2.93vw;font-family:PingFangSC-Regular,PingFang SC;font-weight:400;color:#eafaf8;line-height:4.27vw;white-space:nowrap;opacity:.8}._15rq2xn4{margin-left:3.2vw;width:1.44vw;height:2.4vw}._15rq2xn5{width:.8vw;height:4vw;background:#4C80FF;border-radius:0vw 0vw 26.67vw 26.67vw;z-index:1}._15rq2xn6{margin-top:-1.5vw;width:3.2vw;height:3.2vw}._1d8ur7t0,._7anfuo0{position:relative}._4a4ovk0{margin-bottom:-.4vw;display:flex;flex-direction:column;align-items:center}._4a4ovk1{padding:1.07vw 2.4vw 1.6vw 2.67vw;background:#FFFFFF;box-shadow:0 .53vw 2.4vw #3f3f3f33;border-radius:2.13vw}._4a4ovk2{padding:2.67vw 2.67vw 2.4vw 3.2vw;background:#FFFFFF;box-shadow:0 0 2.67vw #4e556c26;border-radius:4.8vw;font-size:3.2vw;font-family:PingFangSC-Medium,PingFang SC;font-weight:500;color:#1e1e1e;line-height:4.53vw;white-space:nowrap}._4a4ovk3{font-size:3.2vw;font-family:PingFangSC-Medium,PingFang SC;font-weight:500;color:#1e1e1e;line-height:4.53vw;white-space:nowrap}._4a4ovk4{font-size:2.4vw;font-family:PingFangSC-Regular,PingFang SC;font-weight:400;color:#7e7e7e;line-height:3.47vw;white-space:nowrap}._4a4ovk5{color:#4471ff;font-family:PingFangSC-Medium,PingFang SC;font-weight:500}._4a4ovk6{margin-top:1.33vw;width:6.192vw;height:7.998vw}._4a4ovk7{margin-top:2.4vw;width:6.192vw;height:7.998vw}._1l6offo0{display:flex}._1l6offo1{width:4.53vw;height:4.53vw;margin-bottom:-2.265vw}._1l6offo2{width:16.27vw;height:16.27vw;margin-bottom:-8.135vw}.fhyw80{display:flex;align-items:center}.fhyw81{flex-direction:row-reverse}.fhyw83{margin-right:-2.265vw}.fhyw84{margin-left:-2.265vw}.fhyw85{margin-bottom:-2.265vw}.fhyw86{margin-right:calc(50% - 2.265vw)}.fhyw87{margin-left:calc(50% - 2.265vw)}.fhyw88{display:flex;width:4.53vw;height:4.53vw}.fhyw89{opacity:0}.fhyw8b{display:-webkit-box;font-size:3.2vw;font-family:PingFangSC-Semibold,PingFang SC;font-weight:600;color:#243e66;line-height:4.53vw;width:max-content;max-width:32vw;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}._65j3sr1{margin-top:1.87vw;margin-bottom:-5.5vw;width:6vw;height:11vw}@keyframes rrvp0y0{0%{transform:scale(0);opacity:1}33.33%{transform:scale(.5);opacity:1}66.66%{transform:scale(1);opacity:0}to{transform:scale(1);opacity:0}}@keyframes rrvp0y1{0%{transform:scale(0);opacity:0}33.33%{transform:scale(0);opacity:1}66.66%{transform:scale(.5);opacity:1}to{transform:scale(1);opacity:0}}.rrvp0y2{display:flex;margin-bottom:-30.935vw;width:61.87vw;height:61.87vw;animation-name:rrvp0y0;animation-duration:3s;animation-iteration-count:infinite;animation-timing-function:linear}.rrvp0y3{animation-name:rrvp0y1}
@@ -13,3 +13,5 @@ export declare const language2vectorMapForeign: (lang: "en" | "zh") => VectorMap
13
13
  export declare const place2point: (place: Place) => Point;
14
14
  export declare const pipeAsync: <P extends any[]>(fn?: ((...args: P) => any) | undefined) => (...args: P) => void;
15
15
  export declare const geocoderResult2cityName: (value: google.maps.GeocoderResult) => string | undefined;
16
+ export declare const isPlaceEqual: (p1: Place, p2?: Place) => boolean;
17
+ export declare const isPointEqual: (p1: Point, p2?: Point) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heycar/heycars-map",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite -c vite.config.dev.ts",
@@ -42,8 +42,13 @@ export const DemoBusinessRecomendPlace = defineSetup(function DemoBusinessRecome
42
42
  const { lat, lng } = place;
43
43
  console.log("getRecomendPlace context?.source = ", context?.source);
44
44
  return [
45
- { lat: lat + 0.0001, lng: lng + 0.0001, name: "place name 1" },
46
- { lat: lat + 0.002, lng: lng + 0.002, name: "place name 2" },
45
+ {
46
+ lat: lat + 0.1001,
47
+ lng: lng + 0.0001,
48
+ name: "Blessing Koko Dayc are Blessing Koko which is a very long name",
49
+ },
50
+ { lat: lat + 0.0002, lng: lng + 0.0003, name: "place name 2" },
51
+ { lat: lat + 0.0003, lng: lng + 0.0002, name: "place name 3" },
47
52
  ];
48
53
  };
49
54
  const {
@@ -66,7 +71,7 @@ export const DemoBusinessRecomendPlace = defineSetup(function DemoBusinessRecome
66
71
  class={css.demo}
67
72
  geoLoadingTitle={"正在读取GPS位置信息..."}
68
73
  unavailableTitle={"当前区域暂未开通服务"}
69
- noRecomendDescription={"附近无推荐上车地点"}
74
+ noRecomendDescription={""}
70
75
  recomendDescription={"最佳上车地点"}
71
76
  geoDefaultPosition={[139.728287, 35.661007]}
72
77
  getRecomendPlace={getRecomendPlace}
@@ -1,9 +1,20 @@
1
+ import { computed } from "vue-demi";
1
2
  import imgAddressLocator from "../../assets/icons/svg/address-locator.svg";
2
3
  import imgArrowRight from "../../assets/icons/svg/arrow-right.svg";
3
4
  import type { MapLogProps } from "../../hooks/useMapLog";
4
5
  import { defineSetup } from "../../types/helper";
5
6
  import * as css from "./AbsoluteAddressBox.css";
6
7
 
8
+ interface MultilineTitleProps {
9
+ value?: string;
10
+ }
11
+ const MultilineTitle = defineSetup(function MultilineTitle(props: MultilineTitleProps) {
12
+ const linesRef = computed(() => {
13
+ return props.value?.split("\n").flatMap((line, idx) => (idx === 0 ? line : [<br />, line]));
14
+ });
15
+ return () => <div>{linesRef.value}</div>;
16
+ });
17
+
7
18
  export interface AbsoluteAddressBoxProps extends MapLogProps {
8
19
  type: "box" | "locator";
9
20
  title: string;
@@ -27,8 +38,8 @@ export const AbsoluteAddressBox = defineSetup(function AbsoluteAddressBox(
27
38
  <div class={css.absoluteAddressBoxLayout}>
28
39
  <div class={css.absoluteAddressBox} onClick={handleClick}>
29
40
  <div class={css.boxTextLayout}>
30
- <div class={css.boxTitle}>{title}</div>
31
- {description && <div class={css.boxDescription}>{description}</div>}
41
+ <MultilineTitle class={css.boxTitle} value={title} />
42
+ {!!description && <div class={css.boxDescription}>{description}</div>}
32
43
  </div>
33
44
  <img class={css.arrowRight} src={imgArrowRight} />
34
45
  </div>
@@ -8,13 +8,16 @@ import { MapLogProps, useMapLog } from "../../hooks/useMapLog";
8
8
  import type { UseMapPlaceProps } from "../../hooks/useMapPlace";
9
9
  import { useMapRecomendPlace, UseMapRecomendPlaceProps } from "../../hooks/useMapRecomendPlace";
10
10
  import { useMapSupplier } from "../../hooks/useMapSupplier";
11
+ import { useWaitTimer } from "../../hooks/useWaitTimer";
11
12
  import { defineLagecySetup } from "../../types/helper";
12
13
  import type { Place, Point } from "../../types/interface";
14
+ import { isPlaceEqual } from "../../utils/transform";
13
15
  import { AbsoluteAddressBox, AbsoluteAddressBoxProps } from "../AbsoluteAddressBox";
14
16
  import { PassengerCircle } from "../PassengerCircle";
15
17
  import { PickupPoints } from "../PickupPoints";
16
18
 
17
19
  const RECOMMEND_PLACE_LIMIT = 30;
20
+ const MAP_DRAG_END_WAIT_ANIMATION = 500;
18
21
 
19
22
  export interface CenterPlaceSource {
20
23
  source: "default" | "geo" | "drag" | "recomend" | "api";
@@ -45,6 +48,7 @@ export const BusinessRecomendPlaceMap = defineLagecySetup(
45
48
  const { geoDefaultPosition, getRecomendPlace, mapContext } = props;
46
49
  const { mapRef, setMap } = useHeycarMap();
47
50
  const { readyPromise } = useMapSupplier();
51
+ const { timerStart, timerStop } = useWaitTimer({ interval: MAP_DRAG_END_WAIT_ANIMATION });
48
52
  const centerPlace = reactive<Place>({
49
53
  lng: geoDefaultPosition?.[0] ?? 0,
50
54
  lat: geoDefaultPosition?.[1] ?? 0,
@@ -98,6 +102,7 @@ export const BusinessRecomendPlaceMap = defineLagecySetup(
98
102
  mapRef,
99
103
  onChange: (point) => {
100
104
  centerSource.source = "drag";
105
+ timerStart();
101
106
  updatePlace(point);
102
107
  emit("changeByDrag", point);
103
108
  },
@@ -109,6 +114,7 @@ export const BusinessRecomendPlaceMap = defineLagecySetup(
109
114
  updateRecomandPlace,
110
115
  placeCandidates,
111
116
  availableRef,
117
+ isElectedRef,
112
118
  } = useMapRecomendPlace({
113
119
  pointRef: centerPoint,
114
120
  context: centerSource,
@@ -132,7 +138,8 @@ export const BusinessRecomendPlaceMap = defineLagecySetup(
132
138
  }
133
139
  emit("changePlace", place);
134
140
  },
135
- onChange: (place) => {
141
+ onChange: async (place) => {
142
+ await timerStop.promise;
136
143
  centerSource.source = "recomend";
137
144
  Object.assign(centerPlace, { ...place });
138
145
  emit("changeRecomandPlace", place);
@@ -153,15 +160,21 @@ export const BusinessRecomendPlaceMap = defineLagecySetup(
153
160
  : availableRef.value
154
161
  ? centerPlace.name
155
162
  : props.unavailableTitle;
156
- const description = !availableRef.value
157
- ? undefined
158
- : placeCandidates.value.length > 0
159
- ? props.recomendDescription
160
- : props.noRecomendDescription;
163
+ const description =
164
+ !availableRef.value || geoLoading.value
165
+ ? undefined
166
+ : isElectedRef.value
167
+ ? props.recomendDescription
168
+ : props.noRecomendDescription;
161
169
  return (
162
170
  <HeycarMap center={centerPoint.value} zoom={18} mapRef={setMap} log={props.log}>
163
- <PassengerCircle position={geoPosition.value} />
164
- <PickupPoints places={placeCandidates.value} onClick={setCenterByPlace} log={props.log} />
171
+ {!geoError.value && <PassengerCircle position={geoPosition.value} />}
172
+ <PickupPoints
173
+ places={placeCandidates.value}
174
+ renderLabel={(place) => (isPlaceEqual(place, centerPlace) ? undefined : place.name)}
175
+ onClick={setCenterByPlace}
176
+ log={props.log}
177
+ />
165
178
  <AbsoluteAddressBox
166
179
  title={title}
167
180
  description={description}
@@ -1,4 +1,6 @@
1
+ import { reactive, toRef } from "vue-demi";
1
2
  import { MapLogProps, useMapLog } from "../../hooks/useMapLog";
3
+ import { usePointsLabelDirection } from "../../hooks/usePlacesLabelDirection";
2
4
  import { defineSetup } from "../../types/helper";
3
5
  import type { Place } from "../../types/interface";
4
6
  import { place2point } from "../../utils/transform";
@@ -6,20 +8,31 @@ import { PlaceCircle } from "../PlaceCircle";
6
8
 
7
9
  export interface PickupPointsProps extends MapLogProps {
8
10
  places: Place[];
11
+ renderLabel?: (place: Place) => string | undefined;
9
12
  onClick?: (value: Place) => any;
10
13
  }
11
14
  export const PickupPoints = defineSetup(function PickupPoints(props: PickupPointsProps, { emit }) {
12
15
  useMapLog(props, "PickupPoints");
13
- return () => (
14
- <div>
15
- {props.places.map((place) => (
16
- <PlaceCircle
17
- position={place2point(place)}
18
- label={place.name}
19
- textAlign="left"
20
- onClick={() => emit("click", place)}
21
- />
22
- ))}
23
- </div>
16
+ const { renderLabel } = props;
17
+ const directions = usePointsLabelDirection(
18
+ reactive({ places: toRef(props, "places"), heightLimit: 9, widthLimit: 32 })
24
19
  );
20
+ return () => {
21
+ return (
22
+ <div>
23
+ {props.places.map((place, idx) => {
24
+ const direction = directions.value[idx];
25
+ const label = renderLabel ? renderLabel(place) : place.name;
26
+ return (
27
+ <PlaceCircle
28
+ position={place2point(place)}
29
+ label={label}
30
+ textAlign={direction}
31
+ onClick={() => emit("click", place)}
32
+ />
33
+ );
34
+ })}
35
+ </div>
36
+ );
37
+ };
25
38
  });
@@ -62,10 +62,15 @@ export const placeIcon = recipe({
62
62
  });
63
63
 
64
64
  export const label = style({
65
- fontSize: "3.47vw",
65
+ display: "-webkit-box",
66
+ fontSize: "3.2vw",
66
67
  fontFamily: "PingFangSC-Semibold, PingFang SC",
67
68
  fontWeight: 600,
68
69
  color: "#243E66",
69
70
  lineHeight: "4.53vw",
70
- whiteSpace: "nowrap",
71
+ width: "max-content",
72
+ maxWidth: "32vw",
73
+ WebkitLineClamp: 2,
74
+ WebkitBoxOrient: "vertical",
75
+ overflow: "hidden",
71
76
  });
@@ -1,6 +1,8 @@
1
1
  import { ref, watch } from "vue-demi";
2
2
  import type { Point } from "../types/interface";
3
3
 
4
+ const GEO_TIMEOUT = 5000;
5
+
4
6
  export interface UseGeoLocationProps {
5
7
  geoDefaultPosition?: Point;
6
8
  onChange?: (value: { position: Point; coordinate: GeolocationCoordinates }) => any;
@@ -52,8 +54,7 @@ export const useGeoLocation = (props?: UseGeoLocationProps) => {
52
54
  onError?.(error);
53
55
  console.log("errorRef = ", errorRef);
54
56
  },
55
- // todo 要不要 timeout
56
- { timeout: 3000, maximumAge: 0, enableHighAccuracy: false }
57
+ { timeout: GEO_TIMEOUT, maximumAge: 0, enableHighAccuracy: false }
57
58
  );
58
59
  onCleanup(() => navigator.geolocation.clearWatch(watchId));
59
60
  },
@@ -23,7 +23,6 @@ export const useAmapDrag = (props: UseMapDragProps<AMap.Map>) => {
23
23
  });
24
24
  };
25
25
  const emitDragEnd: SetupContextEmitHandler<Pick<AmapProps, "onDragEnd">> = (_, { target }) => {
26
- console.log("on DragEnd ------");
27
26
  isDragging.value = false;
28
27
  const { lng, lat } = target.getCenter();
29
28
  centerRef.value = [lng, lat];
@@ -32,7 +32,6 @@ export const useAmapFitView = (props: UseMapFitViewProps<AMap.Map>) => {
32
32
  if (!map) return;
33
33
  const vw = window.innerWidth / 100;
34
34
  const [top, right, bottom, left] = props.padding ?? [0, 0, 0, 0];
35
- console.log([...overlayGroup]);
36
35
  map.setFitView([...overlayGroup], false, [top * vw, bottom * vw, left * vw, right * vw]);
37
36
  };
38
37
  const registerFitVeiw: RegisterOverlay<AmapOverlay> = {
@@ -0,0 +1,31 @@
1
+ import type { Point } from "../types/interface";
2
+ import { useAmap, useGmap } from "./useMap";
3
+ import { useMapSupplier } from "./useMapSupplier";
4
+
5
+ export const useAmapLngLatToVw = () => {
6
+ const amap = useAmap();
7
+ const apiMapLngLatToVw = (point: Point) => {
8
+ return amap?.value?.lngLatToContainer(point).toArray() as [number, number];
9
+ };
10
+ return { apiMapLngLatToVw };
11
+ };
12
+
13
+ export const useGmapLngLatToVw = () => {
14
+ const gmap = useGmap();
15
+ const apiMapLngLatToVw = (point: Point) => {
16
+ const [lng, lat] = point;
17
+ const projection = gmap?.value?.getProjection();
18
+ const worldCoordinate = projection?.fromLatLngToPoint({ lng, lat });
19
+ const x = worldCoordinate?.x ?? 0;
20
+ const y = worldCoordinate?.y ?? 0;
21
+ const zoom = gmap?.value?.getZoom() ?? 0;
22
+ const scale = 2 ** zoom;
23
+ return [x * scale, y * scale] as [number, number];
24
+ };
25
+ return { apiMapLngLatToVw };
26
+ };
27
+
28
+ export const useMapLngLatToVw = () => {
29
+ const { supplier } = useMapSupplier();
30
+ return supplier === "gmap" ? useGmapLngLatToVw() : useAmapLngLatToVw();
31
+ };
@@ -1,6 +1,7 @@
1
- import { reactive, ref, Ref, watch } from "vue-demi";
1
+ import { computed, reactive, ref, Ref, watch } from "vue-demi";
2
2
  import type { Place, Point } from "../types/interface";
3
- import { place2point } from "../utils/transform";
3
+
4
+ import { isPointEqual, place2point } from "../utils/transform";
4
5
  import { useAmapPlace, useGmapPlace, UseMapPlaceProps } from "./useMapPlace";
5
6
  import { useMapSupplier } from "./useMapSupplier";
6
7
  import { useUpdate } from "./useUdate";
@@ -52,6 +53,12 @@ export const useAmapRecomendPlace = <C>(props: UseMapRecomendPlaceProps<C>) => {
52
53
  onChangeCity,
53
54
  });
54
55
  const recomendPlace = reactive<Place>({ ...place });
56
+ const isElectedRef = computed(() => {
57
+ const point = pointRef.value;
58
+ return placeCandidatesRef.value.some((candidate) =>
59
+ isPointEqual(place2point(candidate), point)
60
+ );
61
+ });
55
62
  watch(
56
63
  () => recomendPlaceKey.value,
57
64
  async () => {
@@ -77,6 +84,7 @@ export const useAmapRecomendPlace = <C>(props: UseMapRecomendPlaceProps<C>) => {
77
84
  recomendPlace,
78
85
  placeCandidates: placeCandidatesRef,
79
86
  availableRef,
87
+ isElectedRef,
80
88
  updateRecomandPlace,
81
89
  updatePlace,
82
90
  setPlace,
@@ -97,6 +105,12 @@ export const useGmapRecomendPlace = <C>(props: UseMapRecomendPlaceProps<C>) => {
97
105
  onChangeCity,
98
106
  });
99
107
  const recomendPlace = reactive<Place>({ ...place });
108
+ const isElectedRef = computed(() => {
109
+ const point = pointRef.value;
110
+ return placeCandidatesRef.value.some((candidate) =>
111
+ isPointEqual(place2point(candidate), point)
112
+ );
113
+ });
100
114
  watch(
101
115
  () => recomendPlaceKey.value,
102
116
  async () => {
@@ -122,6 +136,7 @@ export const useGmapRecomendPlace = <C>(props: UseMapRecomendPlaceProps<C>) => {
122
136
  recomendPlace,
123
137
  placeCandidates: placeCandidatesRef,
124
138
  availableRef,
139
+ isElectedRef,
125
140
  updateRecomandPlace,
126
141
  updatePlace,
127
142
  setPlace,
@@ -0,0 +1,61 @@
1
+ import { ref } from "vue-demi";
2
+ import type { Place, Point } from "../types/interface";
3
+ import { watchEffectForDeepOption } from "../utils/compare";
4
+ import { place2point } from "../utils/transform";
5
+ import { useMapLngLatToVw } from "./useMapLngLatToVw";
6
+
7
+ type LabelDirection = "left" | "right";
8
+ const reverse = (direction: LabelDirection) => (direction === "left" ? "right" : "left");
9
+
10
+ export interface UsePointsLabelDirectionProps {
11
+ places: Place[];
12
+ widthLimit: number;
13
+ heightLimit: number;
14
+ }
15
+ export const usePointsLabelDirection = (props: UsePointsLabelDirectionProps) => {
16
+ const { widthLimit, heightLimit } = props;
17
+ const { apiMapLngLatToVw } = useMapLngLatToVw();
18
+ const diffVw = (from: Point, to: Point) => {
19
+ const vw = 100 / window.innerWidth;
20
+ const [x1, y1] = apiMapLngLatToVw(from);
21
+ const [x2, y2] = apiMapLngLatToVw(to);
22
+ return [(x2 - x1) * vw, (y2 - y1) * vw];
23
+ };
24
+ const directionsRef = ref<LabelDirection[]>(Array(props.places.length).fill("right"));
25
+ watchEffectForDeepOption(
26
+ () => props.places.map(place2point),
27
+ (points) => {
28
+ const sortedPoints = points
29
+ .map((point, index) => ({ point, index }))
30
+ .sort(({ point: [, lat1] }, { point: [, lat2] }) =>
31
+ lat1 < lat2 ? 1 : lat1 > lat2 ? -1 : 0
32
+ );
33
+ const len = points.length;
34
+ const result: LabelDirection[] = Array(len);
35
+ let prevDirection: LabelDirection = "right";
36
+ let isLimited = false;
37
+ for (let idx = 0; idx < len; idx++) {
38
+ const { index } = sortedPoints[idx];
39
+ if (idx === len - 1) {
40
+ result[index] = isLimited ? reverse(prevDirection) : "right";
41
+ continue;
42
+ }
43
+ if (idx === 0) {
44
+ const [dx, dy] = diffVw(sortedPoints[idx].point, sortedPoints[idx + 1].point);
45
+ isLimited = dx > -widthLimit && dx < widthLimit && dy > -heightLimit && dy < heightLimit;
46
+ const direction: LabelDirection = !isLimited ? "right" : dx > 0 ? "left" : "right";
47
+ result[index] = direction;
48
+ prevDirection = direction;
49
+ continue;
50
+ }
51
+ const [dx, dy] = diffVw(sortedPoints[idx].point, sortedPoints[idx + 1].point);
52
+ const direction: LabelDirection = isLimited ? reverse(prevDirection) : "right";
53
+ result[index] = direction;
54
+ prevDirection = direction;
55
+ isLimited = dx > -widthLimit && dx < widthLimit && dy > -heightLimit && dy < heightLimit;
56
+ }
57
+ directionsRef.value = result;
58
+ }
59
+ );
60
+ return directionsRef;
61
+ };
@@ -0,0 +1,25 @@
1
+ interface TimerStop {
2
+ promise: Promise<void>;
3
+ resolve?: () => void;
4
+ }
5
+
6
+ export interface UseWaitTimerProps {
7
+ interval: number;
8
+ }
9
+ export const useWaitTimer = (props: UseWaitTimerProps) => {
10
+ const { interval } = props;
11
+ let timer: number | undefined = undefined;
12
+ const timerStop: TimerStop = {
13
+ promise: Promise.resolve(),
14
+ resolve: undefined,
15
+ };
16
+ const timerStart = () => {
17
+ if (timer) clearTimeout(timer);
18
+ timerStop.resolve?.();
19
+ timerStop.promise = new Promise((resolve) => {
20
+ timerStop.resolve = resolve;
21
+ timer = setTimeout(resolve, interval);
22
+ });
23
+ };
24
+ return { timerStart, timerStop };
25
+ };
@@ -23,7 +23,6 @@ export const useAWalkingRoute = (props: UseWalkingRouteProps) => {
23
23
  return;
24
24
  }
25
25
  const firstRoute = walkingResult.routes[0];
26
- console.log("firstRoute = ", firstRoute);
27
26
  for (const step of firstRoute.steps) {
28
27
  const stepPath = step.path.map<Point>(({ lng, lat }) => [lng, lat]);
29
28
  path = path.concat(stepPath);
@@ -58,3 +58,11 @@ export const geocoderResult2cityName = (value: google.maps.GeocoderResult): stri
58
58
  if (countryIdx > 0) return address_components[countryIdx - 1].long_name;
59
59
  return undefined;
60
60
  };
61
+
62
+ export const isPlaceEqual = (p1: Place, p2?: Place) => {
63
+ return p1.lng === p2?.lng && p1.lat === p2?.lat;
64
+ };
65
+
66
+ export const isPointEqual = (p1: Point, p2?: Point) => {
67
+ return p1[0] === p2?.[0] && p1[1] === p2?.[1];
68
+ };
package/todo.md CHANGED
@@ -1,9 +1,8 @@
1
1
  https://dawchihliou.github.io/articles/building-custom-google-maps-marker-react-component-like-airbnb-in-nextjs
2
2
 
3
3
  2. Google 地址名称太长
4
- 3. 中心点 drag 期间位置
5
- 4. 高德地图加载期间出现大量的请求
6
- 5. google/高德 坐标系转化
4
+ 3. 高德地图加载期间出现大量的请求
5
+ 4. google/高德 坐标系转化
7
6
 
8
7
  ---
9
8
 
@@ -17,5 +16,5 @@ https://dawchihliou.github.io/articles/building-custom-google-maps-marker-react-
17
16
 
18
17
  ---
19
18
 
20
- 1. 定位失败的视觉稿文字提示
21
- 2. 用户地址搜索,先设置台湾,搜个结果,然后切换城市到新加坡,搜索到结果依然是台湾
19
+ 2. google cityparent 要用 city 上级,目前是 country 下级
20
+ 3. 推荐点搜索是距离参数可能要优化,比如没搜到推荐点,应该扩大搜索距离