feat: 地址,逆编码接口联调
This commit is contained in:
parent
fea5af96fa
commit
6bc7d8b362
@ -5,14 +5,23 @@ import ClassifyModal from '@/components/owner/classify';
|
|||||||
import LocationModal from '@/components/owner/location';
|
import LocationModal from '@/components/owner/location';
|
||||||
import PodiumComponent from '@/components/owner/podium';
|
import PodiumComponent from '@/components/owner/podium';
|
||||||
import RankList from '@/components/owner/rankList';
|
import RankList from '@/components/owner/rankList';
|
||||||
|
import { findInnermostElement } from '@/components/owner/utils';
|
||||||
import { ThemedText } from '@/components/ThemedText';
|
import { ThemedText } from '@/components/ThemedText';
|
||||||
|
import { convertRegions } from '@/components/utils/cascaderData';
|
||||||
import { transformData } from '@/components/utils/objectToCascader';
|
import { transformData } from '@/components/utils/objectToCascader';
|
||||||
import { fetchApi } from '@/lib/server-api-util';
|
import { fetchApi } from '@/lib/server-api-util';
|
||||||
import { GroupedData, RankingItem, TargetItem } from '@/types/user';
|
import { GroupedData, RankingItem, TargetItem } from '@/types/user';
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import * as SecureStore from 'expo-secure-store';
|
||||||
import { LayoutChangeEvent, StyleSheet, TouchableOpacity, View } from 'react-native';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { LayoutChangeEvent, Platform, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
interface LocationData {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
children: LocationData[];
|
||||||
|
}
|
||||||
|
|
||||||
export default function OwnerPage() {
|
export default function OwnerPage() {
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -20,7 +29,8 @@ export default function OwnerPage() {
|
|||||||
const [locationModalVisible, setLocationModalVisible] = useState(false);
|
const [locationModalVisible, setLocationModalVisible] = useState(false);
|
||||||
// 分类弹窗
|
// 分类弹窗
|
||||||
const [classifyModalVisible, setClassifyModalVisible] = useState(false);
|
const [classifyModalVisible, setClassifyModalVisible] = useState(false);
|
||||||
|
// 地区数据
|
||||||
|
const [locationData, setLocationData] = useState<CascaderItem[]>([]);
|
||||||
// 在组件内部添加:
|
// 在组件内部添加:
|
||||||
const podiumRef = useRef<View>(null);
|
const podiumRef = useRef<View>(null);
|
||||||
const [podiumPosition, setPodiumPosition] = useState({ x: 0, y: 0, width: 0, height: 0 });
|
const [podiumPosition, setPodiumPosition] = useState({ x: 0, y: 0, width: 0, height: 0 });
|
||||||
@ -28,6 +38,7 @@ export default function OwnerPage() {
|
|||||||
const [classify, setClassify] = useState<TargetItem[]>([]);
|
const [classify, setClassify] = useState<TargetItem[]>([]);
|
||||||
const getClassify = () => {
|
const getClassify = () => {
|
||||||
fetchApi<GroupedData>("/title-tags").then((res: GroupedData) => {
|
fetchApi<GroupedData>("/title-tags").then((res: GroupedData) => {
|
||||||
|
setSelectedClassify([transformData(res)?.[0]?.children?.[0]]);
|
||||||
setClassify(transformData(res));
|
setClassify(transformData(res));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -51,7 +62,6 @@ export default function OwnerPage() {
|
|||||||
};
|
};
|
||||||
// 地区选择
|
// 地区选择
|
||||||
const handleLocationChange = useCallback((selectedItems: CascaderItem[]) => {
|
const handleLocationChange = useCallback((selectedItems: CascaderItem[]) => {
|
||||||
console.log('SelectedLocation:', selectedItems);
|
|
||||||
if (selectedItems.length > 0) {
|
if (selectedItems.length > 0) {
|
||||||
const lastItem = selectedItems[selectedItems.length - 1];
|
const lastItem = selectedItems[selectedItems.length - 1];
|
||||||
// 只有当选择完成时才更新状态
|
// 只有当选择完成时才更新状态
|
||||||
@ -63,7 +73,6 @@ export default function OwnerPage() {
|
|||||||
|
|
||||||
// 分类选择
|
// 分类选择
|
||||||
const handleClassifyChange = useCallback((selectedItems: CascaderItem[]) => {
|
const handleClassifyChange = useCallback((selectedItems: CascaderItem[]) => {
|
||||||
console.log('SelectedClassify:', selectedItems);
|
|
||||||
if (selectedItems.length > 0) {
|
if (selectedItems.length > 0) {
|
||||||
const lastItem = selectedItems[selectedItems.length - 1];
|
const lastItem = selectedItems[selectedItems.length - 1];
|
||||||
// 只有当选择完成时才更新状态
|
// 只有当选择完成时才更新状态
|
||||||
@ -72,15 +81,26 @@ export default function OwnerPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
// 获取本地存储的地址信息
|
||||||
|
const getLocation = async () => {
|
||||||
|
let location;
|
||||||
|
if (Platform.OS === 'web') {
|
||||||
|
location = localStorage.getItem('location');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
location = await SecureStore.getItemAsync('location');
|
||||||
|
|
||||||
|
}
|
||||||
|
return location;
|
||||||
|
};
|
||||||
// 获取排名信息
|
// 获取排名信息
|
||||||
const [ranking, setRanking] = useState<RankingItem[]>([]);
|
const [ranking, setRanking] = useState<RankingItem[]>([]);
|
||||||
const getRanking = () => {
|
const getRanking = () => {
|
||||||
fetchApi<RankingItem[]>("/title-rank", {
|
fetchApi<RankingItem[]>("/title-rank", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
"title_tag_id": selectedClassify?.length > 0 ? selectedClassify[selectedClassify?.length - 1].value : 3,
|
"title_tag_id": selectedClassify?.length > 0 ? selectedClassify[selectedClassify?.length - 1].value : null,
|
||||||
"area_id": 1
|
"area_id": selectedLocation?.length > 0 ? selectedLocation[selectedLocation?.length - 1].value : null
|
||||||
})
|
})
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
setRanking(res);
|
setRanking(res);
|
||||||
@ -89,13 +109,61 @@ export default function OwnerPage() {
|
|||||||
|
|
||||||
// 当用户选择发生变化时,重新获取排名
|
// 当用户选择发生变化时,重新获取排名
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getRanking();
|
console.log('selectedLocation', selectedLocation);
|
||||||
|
console.log('selectedClassify', selectedClassify);
|
||||||
|
if (selectedLocation?.length > 0 && selectedClassify?.length > 0) {
|
||||||
|
getRanking();
|
||||||
|
}
|
||||||
}, [selectedLocation, selectedClassify])
|
}, [selectedLocation, selectedClassify])
|
||||||
|
|
||||||
// 初始化获取分类
|
// 初始化获取分类
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getClassify();
|
const start = async () => {
|
||||||
|
await getClassify();
|
||||||
|
}
|
||||||
|
start();
|
||||||
}, [])
|
}, [])
|
||||||
|
const fetchLocationData = useMemo(() => async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetchApi<LocationData>("/area/tree");
|
||||||
|
const transformed = convertRegions(res?.children, {
|
||||||
|
nameKey: 'name', // 源数据中表示"名称"的字段
|
||||||
|
valueKey: 'id', // 源数据中作为 value 的字段
|
||||||
|
regionsKey: 'children', // 源数据中表示"子级区域"的字段
|
||||||
|
childrenKey: 'children' // 输出结构中表示"子级区域"的字段
|
||||||
|
});
|
||||||
|
return transformed;
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let isMounted = true;
|
||||||
|
|
||||||
|
const loadLocationData = async () => {
|
||||||
|
const data = await fetchLocationData();
|
||||||
|
if (isMounted) {
|
||||||
|
setLocationData(data);
|
||||||
|
// 获取本地存储的地址信息
|
||||||
|
const location = await getLocation();
|
||||||
|
const xuhuiElement = findInnermostElement(data?.filter((item) => {
|
||||||
|
return item.name === JSON.parse(location || "").city
|
||||||
|
}) || [], JSON.parse(location || "").district);
|
||||||
|
if (location) {
|
||||||
|
console.log("xuhuiElement", xuhuiElement);
|
||||||
|
|
||||||
|
setSelectedLocation([xuhuiElement]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadLocationData();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
};
|
||||||
|
}, [fetchLocationData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { paddingTop: insets.top }]}>
|
<View style={[styles.container, { paddingTop: insets.top }]}>
|
||||||
@ -149,6 +217,7 @@ export default function OwnerPage() {
|
|||||||
setModalVisible={setLocationModalVisible}
|
setModalVisible={setLocationModalVisible}
|
||||||
podiumPosition={podiumPosition}
|
podiumPosition={podiumPosition}
|
||||||
handleChange={handleLocationChange}
|
handleChange={handleLocationChange}
|
||||||
|
data={locationData}
|
||||||
/>
|
/>
|
||||||
{/* 分类选择弹窗 */}
|
{/* 分类选择弹窗 */}
|
||||||
<ClassifyModal
|
<ClassifyModal
|
||||||
|
|||||||
@ -19,13 +19,22 @@ const ClassifyModal = (props: ClassifyModalProps) => {
|
|||||||
<Modal
|
<Modal
|
||||||
isVisible={modalVisible}
|
isVisible={modalVisible}
|
||||||
onBackdropPress={() => setModalVisible(false)}
|
onBackdropPress={() => setModalVisible(false)}
|
||||||
swipeDirection="right" // 支持向右滑动关闭
|
swipeDirection={['right']} // 改为数组形式
|
||||||
propagateSwipe={true}
|
propagateSwipe={true}
|
||||||
animationIn="slideInRight" // 入场动画
|
animationIn="slideInRight"
|
||||||
animationOut="slideOutRight" // 出场动画
|
animationOut="slideOutRight"
|
||||||
backdropOpacity={0.5}
|
backdropOpacity={0.5}
|
||||||
onSwipeComplete={() => setModalVisible(false)}
|
onSwipeComplete={() => setModalVisible(false)}
|
||||||
style={{ margin: 0, justifyContent: 'flex-start', marginTop: podiumPosition.height + podiumPosition.y }}
|
onModalHide={() => {
|
||||||
|
// 确保动画完全结束后再更新状态
|
||||||
|
}}
|
||||||
|
useNativeDriver={true} // 启用原生驱动
|
||||||
|
hideModalContentWhileAnimating={true} // 动画时隐藏内容
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
marginTop: podiumPosition.height + podiumPosition.y,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<View style={styles.modalView}>
|
<View style={styles.modalView}>
|
||||||
<View style={styles.modalHeader}>
|
<View style={styles.modalHeader}>
|
||||||
|
|||||||
@ -1,39 +1,41 @@
|
|||||||
import locationData from '@/assets/json/location.json';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||||
import Modal from 'react-native-modal';
|
import Modal from 'react-native-modal';
|
||||||
import Cascader, { CascaderItem } from '../cascader';
|
import Cascader, { CascaderItem } from '../cascader';
|
||||||
import { convertRegions } from '../utils/cascaderData';
|
|
||||||
|
|
||||||
interface LocationModalProps {
|
interface LocationModalProps {
|
||||||
modalVisible: boolean;
|
modalVisible: boolean;
|
||||||
setModalVisible: (visible: boolean) => void;
|
setModalVisible: (visible: boolean) => void;
|
||||||
podiumPosition: { x: number, y: number, width: number, height: number };
|
podiumPosition: { x: number, y: number, width: number, height: number };
|
||||||
handleChange: (selectedItems: CascaderItem[]) => void;
|
handleChange: (selectedItems: CascaderItem[]) => void;
|
||||||
|
data: CascaderItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const LocationModal = React.memo((props: LocationModalProps) => {
|
const LocationModal = React.memo((props: LocationModalProps) => {
|
||||||
const { modalVisible, setModalVisible, podiumPosition, handleChange } = props;
|
const { modalVisible, setModalVisible, podiumPosition, handleChange, data } = props;
|
||||||
const transformed = convertRegions(locationData, {
|
|
||||||
nameKey: 'name', // 源数据中表示"名称"的字段
|
|
||||||
valueKey: 'name', // 源数据中作为 value 的字段
|
|
||||||
regionsKey: 'regions', // 源数据中表示"子级区域"的字段
|
|
||||||
childrenKey: 'children' // 输出结构中表示"子级区域"的字段
|
|
||||||
});
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isVisible={modalVisible}
|
isVisible={modalVisible}
|
||||||
onBackdropPress={() => setModalVisible(false)}
|
onBackdropPress={() => setModalVisible(false)}
|
||||||
swipeDirection="right" // 支持向右滑动关闭
|
swipeDirection={['right']} // 改为数组形式
|
||||||
propagateSwipe={true}
|
propagateSwipe={true}
|
||||||
animationIn="slideInRight" // 入场动画
|
animationIn="slideInRight"
|
||||||
animationOut="slideOutRight" // 出场动画
|
animationOut="slideOutRight"
|
||||||
backdropOpacity={0.5}
|
backdropOpacity={0.5}
|
||||||
onSwipeComplete={() => setModalVisible(false)}
|
onSwipeComplete={() => setModalVisible(false)}
|
||||||
style={{ margin: 0, justifyContent: 'flex-start', marginTop: podiumPosition.height + podiumPosition.y }}
|
onModalHide={() => {
|
||||||
|
// 确保动画完全结束后再更新状态
|
||||||
|
}}
|
||||||
|
useNativeDriver={true} // 启用原生驱动
|
||||||
|
hideModalContentWhileAnimating={true} // 动画时隐藏内容
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
marginTop: podiumPosition.height + podiumPosition.y,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<View style={styles.modalView}>
|
<View style={styles.modalView}>
|
||||||
<View style={styles.modalHeader}>
|
<View style={styles.modalHeader}>
|
||||||
@ -45,7 +47,7 @@ const LocationModal = React.memo((props: LocationModalProps) => {
|
|||||||
</View>
|
</View>
|
||||||
<ScrollView style={styles.modalContent} showsVerticalScrollIndicator={false}>
|
<ScrollView style={styles.modalContent} showsVerticalScrollIndicator={false}>
|
||||||
<Cascader
|
<Cascader
|
||||||
data={transformed}
|
data={data}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@ -3,22 +3,34 @@ import FirstSvg from "@/assets/icons/svg/first.svg";
|
|||||||
import SecondSvg from "@/assets/icons/svg/second.svg";
|
import SecondSvg from "@/assets/icons/svg/second.svg";
|
||||||
import ThirdSvg from "@/assets/icons/svg/third.svg";
|
import ThirdSvg from "@/assets/icons/svg/third.svg";
|
||||||
import { RankingItem } from "@/types/user";
|
import { RankingItem } from "@/types/user";
|
||||||
|
import { useState } from "react";
|
||||||
import { Image, StyleSheet, View } from "react-native";
|
import { Image, StyleSheet, View } from "react-native";
|
||||||
import { ThemedText } from "../ThemedText";
|
import { ThemedText } from "../ThemedText";
|
||||||
interface IPodium {
|
interface IPodium {
|
||||||
data: RankingItem[]
|
data: RankingItem[]
|
||||||
}
|
}
|
||||||
const PodiumComponent = ({ data }: IPodium) => {
|
const PodiumComponent = ({ data }: IPodium) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={[styles.item, { opacity: data[1]?.user_id ? 1 : 0 }]}>
|
<View style={[styles.item, { opacity: data[1]?.user_id ? 1 : 0 }]}>
|
||||||
<SecondSvg />
|
<SecondSvg />
|
||||||
<View style={[styles.titleContainer, { backgroundColor: '#FFB645', borderTopRightRadius: 0, height: 60 }]}>
|
<View style={[styles.titleContainer, { backgroundColor: '#FFB645', borderTopRightRadius: 0, height: 60 }]}>
|
||||||
{
|
{
|
||||||
data[1]?.user_avatar_url
|
(() => {
|
||||||
? <Image source={{ uri: data[1]?.user_avatar_url }} style={{ width: 30, height: 30, borderRadius: 30 }} />
|
const [imageError, setImageError] = useState(false);
|
||||||
: <UserSvg width={30} height={30} />
|
|
||||||
|
if (!data[1]?.user_avatar_url || imageError) {
|
||||||
|
return <UserSvg width={30} height={30} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
source={{ uri: data[1].user_avatar_url }}
|
||||||
|
style={{ width: 30, height: 30, borderRadius: 30 }}
|
||||||
|
onError={() => setImageError(true)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})()
|
||||||
}
|
}
|
||||||
<ThemedText
|
<ThemedText
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
@ -33,9 +45,21 @@ const PodiumComponent = ({ data }: IPodium) => {
|
|||||||
<FirstSvg />
|
<FirstSvg />
|
||||||
<View style={[styles.titleContainer, { backgroundColor: '#E2793F', height: 90 }]}>
|
<View style={[styles.titleContainer, { backgroundColor: '#E2793F', height: 90 }]}>
|
||||||
{
|
{
|
||||||
data[0]?.user_avatar_url
|
(() => {
|
||||||
? <Image source={{ uri: data[0]?.user_avatar_url }} style={{ width: 40, height: 40, borderRadius: 40 }} />
|
const [imageError, setImageError] = useState(false);
|
||||||
: <UserSvg width={40} height={40} />
|
|
||||||
|
if (!data[0]?.user_avatar_url || imageError) {
|
||||||
|
return <UserSvg width={40} height={40} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
source={{ uri: data[0].user_avatar_url }}
|
||||||
|
style={{ width: 40, height: 40, borderRadius: 40 }}
|
||||||
|
onError={() => setImageError(true)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})()
|
||||||
}
|
}
|
||||||
<ThemedText
|
<ThemedText
|
||||||
numberOfLines={2}
|
numberOfLines={2}
|
||||||
@ -50,9 +74,21 @@ const PodiumComponent = ({ data }: IPodium) => {
|
|||||||
<ThirdSvg />
|
<ThirdSvg />
|
||||||
<View style={[styles.titleContainer, { backgroundColor: '#FFD18A', borderTopLeftRadius: 0, height: 50 }]}>
|
<View style={[styles.titleContainer, { backgroundColor: '#FFD18A', borderTopLeftRadius: 0, height: 50 }]}>
|
||||||
{
|
{
|
||||||
data[2]?.user_avatar_url
|
(() => {
|
||||||
? <Image source={{ uri: data[2]?.user_avatar_url }} style={{ width: 20, height: 20, borderRadius: 20 }} />
|
const [imageError, setImageError] = useState(false);
|
||||||
: <UserSvg width={20} height={20} />
|
|
||||||
|
if (!data[2]?.user_avatar_url || imageError) {
|
||||||
|
return <UserSvg width={20} height={20} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
source={{ uri: data[2].user_avatar_url }}
|
||||||
|
style={{ width: 20, height: 20, borderRadius: 20 }}
|
||||||
|
onError={() => setImageError(true)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})()
|
||||||
}
|
}
|
||||||
<ThemedText
|
<ThemedText
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import AtaverSvg from "@/assets/icons/svg/ataver.svg";
|
import AtaverSvg from "@/assets/icons/svg/ataver.svg";
|
||||||
import OwnerSvg from "@/assets/icons/svg/owner.svg";
|
import OwnerSvg from "@/assets/icons/svg/owner.svg";
|
||||||
import { RankingItem } from "@/types/user";
|
import { RankingItem } from "@/types/user";
|
||||||
|
import { useState } from "react";
|
||||||
import { Image, ScrollView, StyleSheet, View } from "react-native";
|
import { Image, ScrollView, StyleSheet, View } from "react-native";
|
||||||
import { ThemedText } from "../ThemedText";
|
import { ThemedText } from "../ThemedText";
|
||||||
interface IRankList {
|
interface IRankList {
|
||||||
@ -37,14 +38,21 @@ const RankList = (props: IRankList) => {
|
|||||||
<ThemedText style={styles.itemRank}>{index + 1}</ThemedText>
|
<ThemedText style={styles.itemRank}>{index + 1}</ThemedText>
|
||||||
<ThemedText style={styles.itemName}>{item.user_nick_name}</ThemedText>
|
<ThemedText style={styles.itemName}>{item.user_nick_name}</ThemedText>
|
||||||
<View style={{ opacity: index == 1 ? 0 : 1 }}>
|
<View style={{ opacity: index == 1 ? 0 : 1 }}>
|
||||||
{item.user_avatar_url ? (
|
{(() => {
|
||||||
<Image
|
const [imageError, setImageError] = useState(false);
|
||||||
source={{ uri: item.user_avatar_url }}
|
|
||||||
style={{ width: 40, height: 40, borderRadius: 40 }}
|
if (!item.user_avatar_url || imageError) {
|
||||||
/>
|
return <AtaverSvg width={40} height={40} />;
|
||||||
) : (
|
}
|
||||||
<AtaverSvg width={40} height={40} />
|
|
||||||
)}
|
return (
|
||||||
|
<Image
|
||||||
|
source={{ uri: item.user_avatar_url }}
|
||||||
|
style={{ width: 40, height: 40, borderRadius: 40 }}
|
||||||
|
onError={() => setImageError(true)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,10 +1,17 @@
|
|||||||
// 地理位置逆编码
|
// 地理位置逆编码
|
||||||
|
import { fetchApi } from '@/lib/server-api-util';
|
||||||
import * as ImagePicker from 'expo-image-picker';
|
import * as ImagePicker from 'expo-image-picker';
|
||||||
import * as Location from 'expo-location';
|
import * as Location from 'expo-location';
|
||||||
import * as Notifications from 'expo-notifications';
|
import * as Notifications from 'expo-notifications';
|
||||||
import * as SecureStore from 'expo-secure-store';
|
import * as SecureStore from 'expo-secure-store';
|
||||||
import { Alert, Linking, Platform } from 'react-native';
|
import { Alert, Linking, Platform } from 'react-native';
|
||||||
|
|
||||||
|
interface Address {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
// Add other address properties as needed
|
||||||
|
}
|
||||||
|
|
||||||
// 配置通知处理器
|
// 配置通知处理器
|
||||||
Notifications.setNotificationHandler({
|
Notifications.setNotificationHandler({
|
||||||
handleNotification: async () => ({
|
handleNotification: async () => ({
|
||||||
@ -16,9 +23,11 @@ Notifications.setNotificationHandler({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 逆编码
|
||||||
export const reverseGeocode = async (latitude: number, longitude: number) => {
|
export const reverseGeocode = async (latitude: number, longitude: number) => {
|
||||||
try {
|
try {
|
||||||
const addressResults = await Location.reverseGeocodeAsync({ latitude, longitude });
|
const addressResults = await fetchApi<Address[]>(`/area/gecoding?latitude=${latitude}&longitude=${longitude}`);
|
||||||
|
console.log('地址:', addressResults);
|
||||||
for (let address of addressResults) {
|
for (let address of addressResults) {
|
||||||
console.log('地址:', address);
|
console.log('地址:', address);
|
||||||
if (Platform.OS === 'web') {
|
if (Platform.OS === 'web') {
|
||||||
@ -29,7 +38,7 @@ export const reverseGeocode = async (latitude: number, longitude: number) => {
|
|||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('逆地理编码失败:', error);
|
console.log('逆地理编码失败:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -299,4 +308,34 @@ export const sendLocalNotification = async (title: string, body: string, data: R
|
|||||||
console.error('发送通知时出错:', error);
|
console.error('发送通知时出错:', error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 获取定位信息 -- 最子集元素
|
||||||
|
export function findInnermostElement(data: any[], targetName: string): { name: string; value: any } | null {
|
||||||
|
let result: { name: string; value: any } | null = null;
|
||||||
|
|
||||||
|
function search(nodes: any[]): boolean {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.name === targetName) {
|
||||||
|
result = { name: node.name, value: node.value };
|
||||||
|
// Keep searching to see if there's a deeper match
|
||||||
|
let foundDeeper = false;
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
foundDeeper = search(node.children);
|
||||||
|
}
|
||||||
|
// If no deeper match was found, this is the innermost one
|
||||||
|
if (!foundDeeper) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (node.children && node.children.length > 0) {
|
||||||
|
const found = search(node.children);
|
||||||
|
if (found) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
search(data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
1
package-lock.json
generated
1
package-lock.json
generated
@ -28,6 +28,7 @@
|
|||||||
"expo-haptics": "~14.1.4",
|
"expo-haptics": "~14.1.4",
|
||||||
"expo-image-manipulator": "~13.1.7",
|
"expo-image-manipulator": "~13.1.7",
|
||||||
"expo-image-picker": "~16.1.4",
|
"expo-image-picker": "~16.1.4",
|
||||||
|
"expo-linear-gradient": "~14.1.5",
|
||||||
"expo-linking": "~7.1.7",
|
"expo-linking": "~7.1.7",
|
||||||
"expo-localization": "^16.1.5",
|
"expo-localization": "^16.1.5",
|
||||||
"expo-location": "~18.1.5",
|
"expo-location": "~18.1.5",
|
||||||
|
|||||||
@ -73,7 +73,8 @@
|
|||||||
"react-native-web": "~0.20.0",
|
"react-native-web": "~0.20.0",
|
||||||
"react-native-webview": "13.13.5",
|
"react-native-webview": "13.13.5",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"expo-clipboard": "~7.1.5"
|
"expo-clipboard": "~7.1.5",
|
||||||
|
"expo-linear-gradient": "~14.1.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user