@heycar/heycars-map 0.3.5 → 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.5",
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 {
@@ -64,10 +69,9 @@ export const DemoBusinessRecomendPlace = defineSetup(function DemoBusinessRecome
64
69
  <SomeAutoComplete cityName="Shinjuku-ku"></SomeAutoComplete>
65
70
  <BusinessRecomendPlaceMap
66
71
  class={css.demo}
67
- geoErrorTitle={"定位获取失败\n请手动设置上车点"}
68
72
  geoLoadingTitle={"正在读取GPS位置信息..."}
69
73
  unavailableTitle={"当前区域暂未开通服务"}
70
- noRecomendDescription={"附近无推荐上车地点"}
74
+ noRecomendDescription={""}
71
75
  recomendDescription={"最佳上车地点"}
72
76
  geoDefaultPosition={[139.728287, 35.661007]}
73
77
  getRecomendPlace={getRecomendPlace}
@@ -39,7 +39,7 @@ export const AbsoluteAddressBox = defineSetup(function AbsoluteAddressBox(
39
39
  <div class={css.absoluteAddressBox} onClick={handleClick}>
40
40
  <div class={css.boxTextLayout}>
41
41
  <MultilineTitle class={css.boxTitle} value={title} />
42
- {description && <div class={css.boxDescription}>{description}</div>}
42
+ {!!description && <div class={css.boxDescription}>{description}</div>}
43
43
  </div>
44
44
  <img class={css.arrowRight} src={imgArrowRight} />
45
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";
@@ -24,7 +27,6 @@ export interface BusinessRecomendPlaceMapProps
24
27
  Pick<UseMapRecomendPlaceProps<CenterPlaceSource>, "getRecomendPlace">,
25
28
  MapLogProps {
26
29
  geoLoadingTitle: string;
27
- geoErrorTitle: string;
28
30
  unavailableTitle: string;
29
31
  recomendDescription: string;
30
32
  noRecomendDescription: string;
@@ -46,6 +48,7 @@ export const BusinessRecomendPlaceMap = defineLagecySetup(
46
48
  const { geoDefaultPosition, getRecomendPlace, mapContext } = props;
47
49
  const { mapRef, setMap } = useHeycarMap();
48
50
  const { readyPromise } = useMapSupplier();
51
+ const { timerStart, timerStop } = useWaitTimer({ interval: MAP_DRAG_END_WAIT_ANIMATION });
49
52
  const centerPlace = reactive<Place>({
50
53
  lng: geoDefaultPosition?.[0] ?? 0,
51
54
  lat: geoDefaultPosition?.[1] ?? 0,
@@ -99,6 +102,7 @@ export const BusinessRecomendPlaceMap = defineLagecySetup(
99
102
  mapRef,
100
103
  onChange: (point) => {
101
104
  centerSource.source = "drag";
105
+ timerStart();
102
106
  updatePlace(point);
103
107
  emit("changeByDrag", point);
104
108
  },
@@ -110,6 +114,7 @@ export const BusinessRecomendPlaceMap = defineLagecySetup(
110
114
  updateRecomandPlace,
111
115
  placeCandidates,
112
116
  availableRef,
117
+ isElectedRef,
113
118
  } = useMapRecomendPlace({
114
119
  pointRef: centerPoint,
115
120
  context: centerSource,
@@ -133,7 +138,8 @@ export const BusinessRecomendPlaceMap = defineLagecySetup(
133
138
  }
134
139
  emit("changePlace", place);
135
140
  },
136
- onChange: (place) => {
141
+ onChange: async (place) => {
142
+ await timerStop.promise;
137
143
  centerSource.source = "recomend";
138
144
  Object.assign(centerPlace, { ...place });
139
145
  emit("changeRecomandPlace", place);
@@ -151,21 +157,24 @@ export const BusinessRecomendPlaceMap = defineLagecySetup(
151
157
  return () => {
152
158
  const title = geoLoading.value
153
159
  ? props.geoLoadingTitle
154
- : geoError.value
155
- ? props.geoErrorTitle
156
160
  : availableRef.value
157
161
  ? centerPlace.name
158
162
  : props.unavailableTitle;
159
163
  const description =
160
- !availableRef.value || geoError.value || geoLoading.value
164
+ !availableRef.value || geoLoading.value
161
165
  ? undefined
162
- : placeCandidates.value.length > 0
166
+ : isElectedRef.value
163
167
  ? props.recomendDescription
164
168
  : props.noRecomendDescription;
165
169
  return (
166
170
  <HeycarMap center={centerPoint.value} zoom={18} mapRef={setMap} log={props.log}>
167
- <PassengerCircle position={geoPosition.value} />
168
- <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
+ />
169
178
  <AbsoluteAddressBox
170
179
  title={title}
171
180
  description={description}
@@ -179,7 +188,6 @@ export const BusinessRecomendPlaceMap = defineLagecySetup(
179
188
  }
180
189
  ).props([
181
190
  "log",
182
- "geoErrorTitle",
183
191
  "geoLoadingTitle",
184
192
  "unavailableTitle",
185
193
  "recomendDescription",
@@ -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
@@ -17,3 +17,4 @@ https://dawchihliou.github.io/articles/building-custom-google-maps-marker-react-
17
17
  ---
18
18
 
19
19
  2. google cityparent 要用 city 上级,目前是 country 下级
20
+ 3. 推荐点搜索是距离参数可能要优化,比如没搜到推荐点,应该扩大搜索距离