@heycar/heycars-map 0.3.5 → 0.3.7

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.
Files changed (35) hide show
  1. package/dist/business-components/BusinessRecomendPlaceMap/BusinessRecomendPlaceMap.d.ts +0 -1
  2. package/dist/business-components/PickupPoints/PickupPoints.d.ts +2 -1
  3. package/dist/components/Gmap/Gmap.css.d.ts +1 -0
  4. package/dist/components/Gmap/Gmap.d.ts +2 -0
  5. package/dist/components/MapProvider/MapProvider.d.ts +2 -0
  6. package/dist/hooks/useMapLngLatToVw.d.ts +10 -0
  7. package/dist/hooks/useMapPlace.d.ts +2 -1
  8. package/dist/hooks/useMapRecomendPlace.d.ts +4 -1
  9. package/dist/hooks/usePlacesLabelDirection.d.ts +9 -0
  10. package/dist/hooks/useWaitTimer.d.ts +12 -0
  11. package/dist/index.cjs +53 -53
  12. package/dist/index.js +1286 -1172
  13. package/dist/style.css +1 -1
  14. package/dist/utils/transform.d.ts +2 -0
  15. package/package.json +1 -1
  16. package/src/App.tsx +1 -1
  17. package/src/Demo/DemoBusinessRecomendPlace.tsx +8 -4
  18. package/src/business-components/AbsoluteAddressBox/AbsoluteAddressBox.tsx +1 -1
  19. package/src/business-components/BusinessRecomendPlaceMap/BusinessRecomendPlaceMap.tsx +42 -16
  20. package/src/business-components/PickupPoints/PickupPoints.tsx +24 -11
  21. package/src/business-components/PlaceCircle/PlaceCircle.css.ts +7 -2
  22. package/src/components/Gmap/Gmap.css.ts +5 -0
  23. package/src/components/Gmap/Gmap.ts +8 -10
  24. package/src/components/MapProvider/MapProvider.tsx +4 -2
  25. package/src/hooks/useGeoLocation.ts +3 -2
  26. package/src/hooks/useMapDrag.ts +0 -1
  27. package/src/hooks/useMapFitView.ts +0 -1
  28. package/src/hooks/useMapLngLatToVw.ts +31 -0
  29. package/src/hooks/useMapPlace.ts +15 -7
  30. package/src/hooks/useMapRecomendPlace.ts +17 -2
  31. package/src/hooks/usePlacesLabelDirection.ts +61 -0
  32. package/src/hooks/useWaitTimer.ts +25 -0
  33. package/src/hooks/useWalkingRoute.ts +0 -1
  34. package/src/utils/transform.ts +8 -0
  35. package/todo.md +1 -0
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{position:relative}._7anfuo0{height:inherit;width:inherit}._7anfuo1{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.7",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite -c vite.config.dev.ts",
package/src/App.tsx CHANGED
@@ -15,7 +15,7 @@ export const App = defineComponent({
15
15
  amapSecret={amapApiSecret}
16
16
  gmapId={gmapId}
17
17
  gmapKey={gmapApiKey}
18
- supplier={"amap"}
18
+ supplier={"gmap"}
19
19
  >
20
20
  <DemoBusinessRecomendPlace />
21
21
  {/* <DemoBusinessQuoting /> */}
@@ -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;
@@ -45,7 +47,8 @@ export const BusinessRecomendPlaceMap = defineLagecySetup(
45
47
  useMapLog(props, "BusinessRecomendPlaceMap");
46
48
  const { geoDefaultPosition, getRecomendPlace, mapContext } = props;
47
49
  const { mapRef, setMap } = useHeycarMap();
48
- const { readyPromise } = useMapSupplier();
50
+ const { readyPromise, supplier } = 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,35 +157,55 @@ 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
- <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} />
169
- <AbsoluteAddressBox
170
- title={title}
171
- description={description}
172
- type={isDragging.value ? "locator" : "box"}
170
+ <HeycarMap
171
+ center={centerPoint.value}
172
+ zoom={18}
173
+ mapRef={setMap}
174
+ renderOverlay={() => {
175
+ return supplier === "gmap" ? (
176
+ <AbsoluteAddressBox
177
+ title={title}
178
+ description={description}
179
+ type={isDragging.value ? "locator" : "box"}
180
+ log={props.log}
181
+ onClick={() => emit("clickLocator")}
182
+ />
183
+ ) : null;
184
+ }}
185
+ log={props.log}
186
+ >
187
+ {!geoError.value && <PassengerCircle position={geoPosition.value} />}
188
+ <PickupPoints
189
+ places={placeCandidates.value}
190
+ renderLabel={(place) => (isPlaceEqual(place, centerPlace) ? undefined : place.name)}
191
+ onClick={setCenterByPlace}
173
192
  log={props.log}
174
- onClick={() => emit("clickLocator")}
175
193
  />
194
+ {supplier === "amap" && (
195
+ <AbsoluteAddressBox
196
+ title={title}
197
+ description={description}
198
+ type={isDragging.value ? "locator" : "box"}
199
+ log={props.log}
200
+ onClick={() => emit("clickLocator")}
201
+ />
202
+ )}
176
203
  </HeycarMap>
177
204
  );
178
205
  };
179
206
  }
180
207
  ).props([
181
208
  "log",
182
- "geoErrorTitle",
183
209
  "geoLoadingTitle",
184
210
  "unavailableTitle",
185
211
  "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,5 +1,10 @@
1
1
  import { style } from "@vanilla-extract/css";
2
2
 
3
3
  export const gmap = style({
4
+ height: "inherit",
5
+ width: "inherit",
6
+ });
7
+
8
+ export const gmapLayout = style({
4
9
  position: "relative",
5
10
  });
@@ -2,7 +2,7 @@ import { computed, h, shallowRef, watchPostEffect } from "vue-demi";
2
2
  import type { SetMap } from "../../hooks/useHeycarMap";
3
3
  import { provideGmap } from "../../hooks/useMap";
4
4
  import { useMapEventSource } from "../../hooks/useMapEventSource";
5
- import { defineSetup } from "../../types/helper";
5
+ import { defineSetup, VueNode } from "../../types/helper";
6
6
  import type { MapEventHandler } from "../../types/mapHelper";
7
7
  import {
8
8
  watchNoneImmediatePostEffectForDeepOption,
@@ -14,12 +14,13 @@ export interface GmapProps extends google.maps.MapOptions {
14
14
  mapRef?: SetMap<google.maps.Map>;
15
15
  onDragStart?: MapEventHandler<google.maps.Map>;
16
16
  onDragEnd?: MapEventHandler<google.maps.Map>;
17
+ renderOverlay?: () => VueNode;
17
18
  }
18
19
 
19
20
  export const Gmap = defineSetup(function Gmap(props: GmapProps, { slots, emit }) {
20
21
  const setMap = props.mapRef;
21
22
  const options = computed(() => {
22
- const { mapRef, onDragStart, onDragEnd, ...options } = props;
23
+ const { renderOverlay, mapRef, onDragStart, onDragEnd, ...options } = props;
23
24
  return options;
24
25
  });
25
26
  const defaultOptions = options.value;
@@ -38,13 +39,6 @@ export const Gmap = defineSetup(function Gmap(props: GmapProps, { slots, emit })
38
39
  mapRef.value = map!;
39
40
  setMap?.(map!);
40
41
  });
41
- // watch(
42
- // () => options.value.zoom,
43
- // (zoom) => {
44
- // if (zoom) mapRef.value?.setZoom(zoom);
45
- // },
46
- // { flush: "post" }
47
- // );
48
42
  watchNoneImmediatePostEffectForDeepOption(
49
43
  () => options.value,
50
44
  (options) => {
@@ -52,5 +46,9 @@ export const Gmap = defineSetup(function Gmap(props: GmapProps, { slots, emit })
52
46
  }
53
47
  );
54
48
  watchPostEffectForGMapEvent(mapRef, props, handleDrag, ["onDragStart", "onDragEnd"]);
55
- return () => h("div", { ref: elementRef, class: css.gmap }, slots.default?.());
49
+ return () =>
50
+ h("div", { class: css.gmapLayout }, [
51
+ props.renderOverlay?.(),
52
+ h("div", { ref: elementRef, class: css.gmap }, slots.default?.()),
53
+ ]);
56
54
  });
@@ -29,6 +29,7 @@ export interface HeycarMapProps
29
29
  Pick<AmapProps, "center" | "zoom">,
30
30
  MapLogProps {
31
31
  mapRef?: SetMap<AMap.Map> | SetMap<google.maps.Map>;
32
+ renderOverlay?: GmapProps["renderOverlay"];
32
33
  onDragEnd?: (value: [number, number]) => any;
33
34
  }
34
35
  export const HeycarMap = defineLagecySetup(function HeycarMap(
@@ -47,7 +48,7 @@ export const HeycarMap = defineLagecySetup(function HeycarMap(
47
48
  };
48
49
  return () => {
49
50
  const { supplier, gmapId, language = "zh", status } = payload;
50
- const { mapRef: setMap, center, zoom } = props;
51
+ const { mapRef: setMap, center, zoom, renderOverlay } = props;
51
52
  if (status === Status.LOADING) return slots.loading?.();
52
53
  if (status === Status.FAILURE) return slots.fallback?.();
53
54
  const children = slots.default?.();
@@ -59,6 +60,7 @@ export const HeycarMap = defineLagecySetup(function HeycarMap(
59
60
  mapId={gmapId}
60
61
  center={center ? vec2lnglat(center) : undefined}
61
62
  zoom={zoom}
63
+ renderOverlay={renderOverlay}
62
64
  onDragEnd={gmapDebouncedEmit}
63
65
  >
64
66
  {children}
@@ -78,4 +80,4 @@ export const HeycarMap = defineLagecySetup(function HeycarMap(
78
80
  );
79
81
  }
80
82
  };
81
- }).props(["center", "fallback", "loading", "mapRef", "zoom"]);
83
+ }).props(["center", "fallback", "loading", "mapRef", "zoom", "renderOverlay"]);
@@ -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
+ };
@@ -8,6 +8,7 @@ import { useUpdate } from "./useUdate";
8
8
  export interface UseMapPlaceOnChangeCityProps {
9
9
  cityName?: string;
10
10
  cityParentName?: string;
11
+ countryName?: string;
11
12
  }
12
13
  export interface UseMapPlaceProps {
13
14
  pointRef: Ref<Point>;
@@ -33,7 +34,7 @@ export const useAmapPlace = (props: UseMapPlaceProps) => {
33
34
  const { addressComponent } = (result as AMap.ReGeocoderResult).regeocode;
34
35
  const cityName = addressComponent.city ? addressComponent.city : addressComponent.province;
35
36
  const cityParentName = addressComponent.province;
36
- onChangeCity?.({ cityName, cityParentName });
37
+ onChangeCity?.({ cityName, cityParentName, countryName: "" });
37
38
  });
38
39
  };
39
40
  const updatePlace = (value: Point) => {
@@ -64,7 +65,7 @@ export const useAmapPlace = (props: UseMapPlaceProps) => {
64
65
  place.lng = lng;
65
66
  place.lat = lat;
66
67
  onChange?.({ lng, lat, name });
67
- onChangeCity?.({ cityName, cityParentName });
68
+ onChangeCity?.({ cityName, cityParentName, countryName: "" });
68
69
  return;
69
70
  }
70
71
 
@@ -82,14 +83,20 @@ export const useAmapPlace = (props: UseMapPlaceProps) => {
82
83
  };
83
84
 
84
85
  export const useGmapPlace = (props: UseMapPlaceProps) => {
85
- const { pointRef, onChange, onChangeCity } = props;
86
+ const { onChange, onChangeCity } = props;
87
+ const defaultPoint: Point = [...props.pointRef.value];
88
+ const pointRef = ref<Point>(defaultPoint);
86
89
  const { readyPromise } = useMapSupplier();
87
- const { idx: placeKey, update: updatePlace } = useUpdate();
90
+ const { idx: placeKey, update } = useUpdate();
88
91
  const place = reactive<Place>({
89
92
  lng: pointRef.value[0],
90
93
  lat: pointRef.value[1],
91
94
  name: "",
92
95
  });
96
+ const updatePlace = (value: Point) => {
97
+ pointRef.value = value;
98
+ update();
99
+ };
93
100
  const setPlace = (value: Place) => {
94
101
  place.lng = value.lng;
95
102
  place.lat = value.lat;
@@ -101,7 +108,8 @@ export const useGmapPlace = (props: UseMapPlaceProps) => {
101
108
  const { results } = await geocoder.geocode({ language: LANGUAGE, location: { lng, lat } });
102
109
  const cityName = geocoderResult2cityName(results[0]);
103
110
  const cityParentName = results.slice(-2)[0].address_components[0].long_name;
104
- onChangeCity?.({ cityName, cityParentName });
111
+ const countryName = results.slice(-1)[0].address_components[0].long_name;
112
+ onChangeCity?.({ cityName, cityParentName, countryName });
105
113
  };
106
114
  watch(
107
115
  () => placeKey.value,
@@ -113,12 +121,12 @@ export const useGmapPlace = (props: UseMapPlaceProps) => {
113
121
  const name = results[0].formatted_address;
114
122
  const cityName = geocoderResult2cityName(results[0]);
115
123
  const cityParentName = results.slice(-2)[0].address_components[0].long_name;
116
- // const countryName = results.slice(-1)[0].address_components[0].long_name;
124
+ const countryName = results.slice(-1)[0].address_components[0].long_name;
117
125
  place.lng = lng;
118
126
  place.lat = lat;
119
127
  place.name = name;
120
128
  onChange?.({ lng, lat, name });
121
- onChangeCity?.({ cityName, cityParentName });
129
+ onChangeCity?.({ cityName, cityParentName, countryName });
122
130
  }
123
131
  );
124
132
  return { place, updatePlace, setPlace, updateCity };
@@ -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
+ };