引言
在《加油站小程序实战教程07城市管理》中,我们介绍了如何在后台维护城市的基础信息并且站点和城市进行了关联。有了基础信息之后,我们就要用地图要求的数据结构进行组装。本篇我们介绍如何利用API封装数据,让城市列表和地图里的站点信息都来源于数据库。
1 创建API
切换到APIs,点击创建图标,新建API
选择自定义代码
录入名称和标识
修改方法名称和标识
2 实现业务逻辑
首先看一下我们组件要求的数据结构
const cities = [
{ name: "北京", location: [116.397428, 39.90923], stations: [{ name: "天安门", location: [116.397428, 39.90923] }, { name: "鸟巢", location: [116.397524, 39.992424] }] },
{ name: "上海", location: [121.473701, 31.230416], stations: [{ name: "外滩", location: [121.490317, 31.241638] }, { name: "陆家嘴", location: [121.502771, 31.238068] }] },
{ name: "广州", location: [113.264385, 23.129112], stations: [{ name: "广州塔", location: [113.330863, 23.113455] }, { name: "白云山", location: [113.28848, 23.168778] }] },
{ name: "深圳市", location: [114.057868, 22.543099], stations: [{ name: "世界之窗", location: [113.976373, 22.53332] }, { name: "欢乐谷", location: [113.998048, 22.546054] }] },
{ name: "成都", location: [104.066541, 30.572269], stations: [{ name: "宽窄巷子", location: [104.062837, 30.667493] }, { name: "大熊猫基地", location: [104.138817, 30.735778] }] },
{ name: "呼和浩特市", location: [111.75199, 40.84211], stations: [
{ name: "大召寺", location: [111.692018, 40.812225] },
{ name: "昭君墓", location: [111.930514, 40.718719] },
{ name: "呼和浩特火车站", location: [111.75199, 40.841939] },
{ name: "五塔寺", location: [111.695302, 40.809052] },
{ name: "敕勒川草原", location: [111.81666, 40.88189] },
{ name: "内蒙古博物院", location: [111.704164, 40.818445] },
] },
];
我们的结构要求先是城市,城市里再显示站点信息。原来我们没办法一次性得到这个结构,新版数据源在主表里就可以获取到这些信息。我们先获取一下城市信息
2.1 自定义代码中获取主表信息
在自定义代码中我们可以通过调用数据源的获取多条方法得到主表信息,然后进行一定的转换得到我们需要的数据结构,具体代码如下
module.exports = async function (params, context) {
const result = await context.callModel({
name:'City',
methodName: "wedaGetRecordsV2",
params: {
// 排序
orderBy: [
{
xh: "asc", // 创建时间,倒序
},
],
// 返回字段选择
select: {
name: true,
longitude:true,
latitude:true,
xh:true,
_id:true,
ctiyGasStation:true
},
// 返回total字段
getCount: true,
// 页面大小
pageSize: 200,
// 当前页面
pageNumber: 1,
compatibleWithV1: true,
},
});
const city = result.records.map((record) => {
return {
name: record.name,
location: [record.longitude, record.latitude],
stations: record.ctiyGasStation.map((station) => ({
name: station.name,
location: [record.longitude, record.latitude], // 使用城市的经纬度替代,因为原数据中站点没有单独经纬度
})),
};
});
return city;
};
贴入代码后,点击方法测试,可以看到结果,点击出参自动映射完成我们API的编写
2.2 创建变量读取API数据
API编写好之后,我们在页面上需要通过变量读取一下数据。在代码区新建一个外部API查询
选择我们的API,触发方式要设置为手动触发
2.3 修改组件初始化城市数据
在上一个版本,我们的城市信息是通过变量设置的默认值,像这种需要通过外部API去加载数据的,在react中需要通过设置状态变量来读取,先创建一个状态变量
const [cities, setCities] = useState([]); // 存储城市数据
然后添加一个钩子函数,来动态的设置我们的城市数据
useEffect(() => {
// 模拟触发 API 并获取数据
const fetchCityStations = async () => {
try {
// 调用你的 API 获取城市数据
const result = await $w.getCityStation.trigger();
console.log("城市数据",result)
if (result) {
setCities(result); // 更新城市数据
} else {
console.error("获取城市数据失败");
}
} catch (error) {
console.error("API 调用失败", error);
}
};
fetchCityStations();
}, []); // 只在组件加载时触发一次
完整代码
import React, { useRef, useEffect, useState } from "react";
export default function CitySwitcherMap(props) {
const { style } = props;
const mapContainerRef = useRef(null);
const [selectedCity, setSelectedCity] = useState("");
const [defaultCityLocation, setDefaultCityLocation] = useState([116.397428, 39.90923]); // 默认北京
const [mapInstance, setMapInstance] = useState(null);
const [userLocation, setUserLocation] = useState([116.397428, 39.90923]); // 默认用户位置为北京
const [nearestStation, setNearestStation] = useState(null);
const [cities, setCities] = useState([]); // 存储城市数据
useEffect(() => {
// 模拟触发 API 并获取数据
const fetchCityStations = async () => {
try {
// 调用你的 API 获取城市数据
const result = await $w.getCityStation.trigger();
console.log("城市数据",result)
if (result) {
setCities(result); // 更新城市数据
} else {
console.error("获取城市数据失败");
}
} catch (error) {
console.error("API 调用失败", error);
}
};
fetchCityStations();
}, []); // 只在组件加载时触发一次
useEffect(() => {
const loadAMap = async () => {
if (!window.AMap) {
const script = document.createElement("script");
script.src = "https://webapi.amap.com/maps?v=2.0&key=6bd36e95532ee2800d841762601420f5";
script.async = true;
script.onload = () => {
initMap();
};
document.body.appendChild(script);
} else {
initMap();
}
};
const initMap = () => {
if (window.AMap) {
const map = new window.AMap.Map(mapContainerRef.current, {
center: defaultCityLocation,
zoom: 12,
mapStyle: "amap://styles/normal",
});
setMapInstance(map);
}
};
loadAMap();
}, []);
useEffect(() => {
if (mapInstance) {
const selectedCityData = cities.find((city) => city.name === selectedCity);
if (selectedCityData) {
mapInstance.setCenter(selectedCityData.location);
addMarkers(selectedCityData.stations);
calculateNearestStation(selectedCityData.stations);
}
}
}, [selectedCity, mapInstance, cities]);
const addMarkers = (stations) => {
mapInstance.clearMap();
stations.forEach((station) => {
const marker = new window.AMap.Marker({
position: station.location,
map: mapInstance,
});
const distance = calculateDistance(userLocation, station.location);
const infoWindow = new window.AMap.InfoWindow({
content: `<div style="padding: 10px; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
<div><strong>${station.name}</strong></div>
<div style="color: #666;">距当前站点 ${distance.toFixed(1)} km</div>
</div>`,
offset: new window.AMap.Pixel(0, -40), // 调整偏移,避免遮挡标记
});
marker.on("click", () => {
infoWindow.open(mapInstance, marker.getPosition());
});
if (nearestStation && nearestStation.name === station.name) {
infoWindow.open(mapInstance, marker.getPosition());
}
});
};
const calculateNearestStation = (stations) => {
let nearest = null;
let minDistance = Infinity;
stations.forEach((station) => {
const distance = calculateDistance(userLocation, station.location);
if (distance < minDistance) {
minDistance = distance;
nearest = station;
}
});
setNearestStation(nearest);
};
const calculateDistance = (start, end) => {
const startLngLat = new window.AMap.LngLat(start[0], start[1]);
const endLngLat = new window.AMap.LngLat(end[0], end[1]);
return startLngLat.distance(endLngLat) / 1000; // 返回公里数
};
const getUserLocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
setUserLocation([longitude, latitude]);
fetchCityNameFromCoords(longitude, latitude);
},
() => {
setUserLocation([111.75199, 40.84211]); // 呼和浩特
setSelectedCity("呼和浩特市");
}
);
}
};
const fetchCityNameFromCoords = async (lng, lat) => {
try {
const response = await fetch(
`https://restapi.amap.com/v3/geocode/regeo?key=154efad008972a03ecac218a844959a9&location=${lng},${lat}`
);
const data = await response.json();
if (data.status === "1" && data.regeocode) {
const cityName = data.regeocode.addressComponent.city || data.regeocode.addressComponent.province;
setSelectedCity(cityName);
}
} catch {
console.error("获取城市名称失败");
}
};
useEffect(() => {
getUserLocation();
}, []);
return (
<div>
<div style={{ marginBottom: "10px", zIndex: 999 }}>
<select
value={selectedCity}
onChange={(e) => setSelectedCity(e.target.value)}
style={{
padding: "8px",
fontSize: "14px",
borderRadius: "4px",
border: "1px solid #ccc",
}}
>
<option value="">请选择城市</option>
{cities.map((city) => (
<option key={city.name} value={city.name}>
{city.name}
</option>
))}
</select>
</div>
<div
ref={mapContainerRef}
style={{
position: "relative",
width: "100%",
height: "500px",
marginTop: "0px",
...style,
}}
/>
</div>
);
}
最终效果
程序运行后,会自动读取数据库里的城市及站点信息,渲染到地图里
总结
本篇我们介绍了如何通过API去将主子表的数据一并取出,并且重新改造成我们需要的数据结构。前端通过变量读取API,并且讲解了react中的状态变量及钩子函数的具体用法。有粉丝像举一反三实现他自己的应用,这就需要理解教程里涉及的知识点,只有掌握了才能做出自己需要的应用来。