提交 ee1b67e7 作者: ZhangLingKun

功能:用户定位展示(IP位置)

上级 328e197f
流水线 #7302 已通过 于阶段
in 5 分 2 秒
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
"@amap/amap-jsapi-loader": "^1.0.1", "@amap/amap-jsapi-loader": "^1.0.1",
"@ant-design/cssinjs": "^1.17.2", "@ant-design/cssinjs": "^1.17.2",
"@ant-design/icons": "^5.2.6", "@ant-design/icons": "^5.2.6",
"@reduxjs/toolkit": "^1.9.7",
"@types/styled-components": "^5.1.29", "@types/styled-components": "^5.1.29",
"antd": "^5.11.0", "antd": "^5.11.0",
"axios": "^1.6.0", "axios": "^1.6.0",
...@@ -30,9 +31,13 @@ ...@@ -30,9 +31,13 @@
"js-base64": "^3.7.5", "js-base64": "^3.7.5",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"next": "14.0.1", "next": "14.0.1",
"next-redux-wrapper": "^8.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-infinite-scroll-component": "^6.1.0", "react-infinite-scroll-component": "^6.1.0",
"react-redux": "^8.1.3",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"styled-components": "^6.1.0" "styled-components": "^6.1.0"
}, },
"devDependencies": { "devDependencies": {
......
...@@ -2,7 +2,11 @@ import React, { useEffect } from 'react'; ...@@ -2,7 +2,11 @@ import React, { useEffect } from 'react';
import { EnvironmentFilled } from '@ant-design/icons'; import { EnvironmentFilled } from '@ant-design/icons';
import { Button } from 'antd'; import { Button } from 'antd';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux';
import { HeaderWrap } from '@/components/layout/header/styled'; import { HeaderWrap } from '@/components/layout/header/styled';
import { RootState } from '@/store';
import { AddressState, setAddress } from '@/store/address';
import getLocationByIP from '@/utils/getLocationByIP';
const HeaderView: React.FC<{ const HeaderView: React.FC<{
placeholder?: boolean; placeholder?: boolean;
...@@ -12,9 +16,19 @@ const HeaderView: React.FC<{ ...@@ -12,9 +16,19 @@ const HeaderView: React.FC<{
}; };
// 当前的路由数据 // 当前的路由数据
const router = useRouter(); const router = useRouter();
// store
const dispatch = useDispatch();
// address
const address = useSelector(
(state: RootState) => state.address,
) as AddressState;
// 组件挂载 // 组件挂载
useEffect(() => { useEffect(() => {
console.log('HeaderView --->', router); // console.log('HeaderView --->', router);
getLocationByIP().then((res) => {
dispatch(setAddress(res));
});
console.log('address --->', address);
}, [router]); }, [router]);
return ( return (
<HeaderWrap> <HeaderWrap>
...@@ -26,9 +40,9 @@ const HeaderView: React.FC<{ ...@@ -26,9 +40,9 @@ const HeaderView: React.FC<{
/> />
</div> </div>
<Button type={'link'} className="location-address"> <Button type={'link'} className="location-address">
杭州 {address?.city || '定位中...'}
</Button> </Button>
<div className="location-hello">Hi,欢迎来云享飞</div> <div className="location-hello">Hi,欢迎来云享飞</div>
</div> </div>
<div className="header-nav"> <div className="header-nav">
<div className="nav-tab"> <div className="nav-tab">
......
...@@ -2,7 +2,8 @@ import { FC, useEffect } from 'react'; ...@@ -2,7 +2,8 @@ import { FC, useEffect } from 'react';
const MapContainer: FC<{ const MapContainer: FC<{
list: { lat: number; lon: number; name: string }[]; list: { lat: number; lon: number; name: string }[];
}> = ({ list }) => { center?: [number, number];
}> = ({ list, center }) => {
// 地图实例 // 地图实例
let map: any = null; let map: any = null;
// 高德地图 // 高德地图
...@@ -58,7 +59,7 @@ const MapContainer: FC<{ ...@@ -58,7 +59,7 @@ const MapContainer: FC<{
// 设置地图容器id // 设置地图容器id
viewMode: '3D', // 是否为3D地图模式 viewMode: '3D', // 是否为3D地图模式
zoom: 10, // 初始化地图级别 zoom: 10, // 初始化地图级别
center: [119.96043, 30.04885], // 初始化地图中心点位置 center: center || [119.96043, 30.04885], // 初始化地图中心点位置
}); });
// 用户定位 // 用户定位
map.plugin('AMap.Geolocation', () => { map.plugin('AMap.Geolocation', () => {
...@@ -89,7 +90,7 @@ const MapContainer: FC<{ ...@@ -89,7 +90,7 @@ const MapContainer: FC<{
return () => { return () => {
map?.destroy(); map?.destroy();
}; };
}, [list]); }, [list, center]);
return ( return (
<div <div
id="container" id="container"
......
import React from 'react'; import React from 'react';
import { ConfigProvider, theme } from 'antd'; import { ConfigProvider, theme } from 'antd';
// eslint-disable-next-line import/order
import type { AppProps } from 'next/app'; import type { AppProps } from 'next/app';
import '../styles/animate.css'; import '../styles/animate.css';
import '../styles/globals.css'; import '../styles/globals.css';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { wrapper } from '@/store';
import themeConfig from '../theme/themeConfig'; import themeConfig from '../theme/themeConfig';
const App = ({ Component, pageProps }: AppProps) => ( const App = ({ Component, pageProps, ...rest }: AppProps) => {
<ConfigProvider const { store } = wrapper.useWrappedStore(rest);
theme={{
algorithm: theme.compactAlgorithm, return (
...themeConfig, <Provider store={store}>
}} {/* eslint-disable-next-line no-underscore-dangle */}
> <PersistGate persistor={(store as any)?.__persistor} loading={null}>
<Component {...pageProps} /> <ConfigProvider
</ConfigProvider> theme={{
); algorithm: theme.compactAlgorithm,
...themeConfig,
}}
>
<Component {...pageProps} />
</ConfigProvider>
</PersistGate>
</Provider>
);
};
export default App; export default App;
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { HomeAPI } from '@/api'; import { HomeAPI } from '@/api';
import MapContainer from '@/components/map'; import MapContainer from '@/components/map';
import { RootState } from '@/store';
import { AddressState } from '@/store/address';
const HomeMapView = () => { const HomeMapView = () => {
// 选择索引 // 选择索引
...@@ -18,6 +21,10 @@ const HomeMapView = () => { ...@@ -18,6 +21,10 @@ const HomeMapView = () => {
setCurrentIndex(index); setCurrentIndex(index);
getServiceBitmap().then(); getServiceBitmap().then();
}; };
// address
const address = useSelector(
(state: RootState) => state.address,
) as AddressState;
// 地图网点列表 // 地图网点列表
const [mapMarkerList, setMapMarkerList] = useState< const [mapMarkerList, setMapMarkerList] = useState<
{ lat: number; lon: number; name: string }[] { lat: number; lon: number; name: string }[]
...@@ -46,7 +53,10 @@ const HomeMapView = () => { ...@@ -46,7 +53,10 @@ const HomeMapView = () => {
}, []); }, []);
return ( return (
<HomeMapWrap> <HomeMapWrap>
<MapContainer list={mapMarkerList} /> <MapContainer
list={mapMarkerList}
center={[address?.longitude, address?.latitude]}
/>
<div className="map-wrap flex-around"> <div className="map-wrap flex-around">
{networkTypeList?.map((i, j) => ( {networkTypeList?.map((i, j) => (
<div <div
......
// user.ts
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
import { LocationType } from '@/utils/getLocationByIP';
export type AddressState = LocationType;
const initialState: AddressState | {} = {};
const addressSlice = createSlice({
name: 'address',
initialState,
reducers: {
setAddress: (state, action) => {
return action.payload;
},
},
extraReducers: {
// hydrated 用于获取服务端注入的state并选择更新
[HYDRATE]: (state, action) => {
return {
...action.payload.address,
};
},
},
});
export const { setAddress } = addressSlice.actions;
export default addressSlice.reducer;
// index.ts
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import { createWrapper } from 'next-redux-wrapper';
import { persistReducer, persistStore } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import addressReducer from './address';
import userReducer from './user';
// 单独创建rootReducer供服务端和客户端创建store使用;
const rootReducer = combineReducers({
user: userReducer,
address: addressReducer,
});
const makeStore = () => {
const isServer = typeof window === 'undefined';
// 区分客户端和服务端,服务端不需要持久存储,客户端存在在localStorage中;
if (isServer) {
return configureStore({
reducer: rootReducer,
devTools: true,
});
}
const persistConfig = {
key: 'SHAREFLY-WEB-STORAGE',
whiteList: ['user', 'address'],
storage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = configureStore({
reducer: persistedReducer,
devTools: process.env.NODE_ENV !== 'production',
});
// @ts-ignore 只使用客户端渲染不需要此种做法,只需导出persistor即可;
// eslint-disable-next-line no-underscore-dangle
(store as any).__persistor = persistStore(store);
return store;
};
export type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType<AppStore['getState']>;
export const wrapper = createWrapper(makeStore);
// user.ts
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
// @ts-ignore
export type UserState = API.User.UserInfo;
const initialState: UserState = {
username: '', // 示例
avatar: '',
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setUser: (state, action) => {
return action.payload;
},
},
extraReducers: {
// hydrated 用于获取服务端注入的state并选择更新
[HYDRATE]: (state, action) => {
return {
...action.payload.user,
};
},
},
});
export const { setUser } = userSlice.actions;
export default userSlice.reducer;
import { message } from 'antd';
import Axios from 'axios';
// 位置的类型
export interface LocationType {
ip: string;
network: string;
version: string;
city: string;
region: string;
region_code: string;
country: string;
country_name: string;
country_code: string;
country_code_iso3: string;
country_capital: string;
country_tld: string;
continent_code: string;
in_eu: boolean;
postal: null;
latitude: number;
longitude: number;
timezone: string;
utc_offset: string;
country_calling_code: string;
currency: string;
currency_name: string;
languages: string;
country_area: number;
country_population: number;
asn: string;
org: string;
status: string;
info: string;
infocode: string;
province: string;
adcode: string;
rectangle: string;
}
// 根据用户的ip地址获取当前位置
export default function getLocationByIP(): Promise<LocationType> {
return new Promise((resolve, reject) => {
// 用户的ip信息
let ipInfo: LocationType;
// 获取用户的ip信息
const getIPInfo = async () => {
const res = await Axios.get('https://ipapi.co/json/');
if (res && res?.status === 200) {
ipInfo = res.data;
// console.log('获取位置1 --->', ipInfo);
return;
}
message.warning('获取位置失败').then();
};
// 根据ip信息获取位置信息
const getLocationByIPInfo = async () => {
const res = await Axios.get(
`https://restapi.amap.com/v3/ip?key=4f98d03f611b80f95a0ad56b044ffb59&ip=${ipInfo.ip}`,
);
if (res && res?.status === 200) {
const { province, city, adcode, rectangle } = res.data;
ipInfo.province = province;
ipInfo.city = city;
ipInfo.adcode = adcode;
ipInfo.rectangle = rectangle;
// console.log('获取位置 --->', ipInfo);
resolve(ipInfo);
return;
}
message.warning('获取位置失败').then();
reject(ipInfo);
};
// 自执行
(async () => {
await getIPInfo();
await getLocationByIPInfo();
})();
});
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论