351 lines
14 KiB
TypeScript
351 lines
14 KiB
TypeScript
import DownSvg from '@/assets/icons/svg/down.svg';
|
|
import PlaceSvg from '@/assets/icons/svg/place.svg';
|
|
import ReturnArrowSvg from '@/assets/icons/svg/returnArrow.svg';
|
|
import SearchSvg from '@/assets/icons/svg/search.svg';
|
|
import { CascaderItem } from '@/components/cascader';
|
|
import ClassifyModal from '@/components/owner/classify';
|
|
import LocationModal from '@/components/owner/location';
|
|
import PodiumComponent from '@/components/owner/podium';
|
|
import RankList from '@/components/owner/rankList';
|
|
import { findInnermostElement } from '@/components/owner/utils';
|
|
import { ThemedText } from '@/components/ThemedText';
|
|
import { convertRegions } from '@/components/utils/cascaderData';
|
|
import { transformData } from '@/components/utils/objectToCascader';
|
|
import { fetchApi } from '@/lib/server-api-util';
|
|
import { GroupedData, RankingItem, TargetItem } from '@/types/user';
|
|
import { useRouter } from "expo-router";
|
|
import * as SecureStore from 'expo-secure-store';
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
import { Keyboard, LayoutChangeEvent, Platform, StyleSheet, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
|
|
import { TextInput } from 'react-native-gesture-handler';
|
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
interface LocationData {
|
|
id: number;
|
|
name: string;
|
|
children: LocationData[];
|
|
}
|
|
|
|
export default function OwnerPage() {
|
|
const insets = useSafeAreaInsets();
|
|
const router = useRouter();
|
|
const HOT_CITIES = [
|
|
['北京', '上海', '广州', '深圳'],
|
|
['杭州', '成都', '乌鲁木齐', '武汉'],
|
|
['西安', '重庆', '西宁', '哈尔滨'],
|
|
['长沙', '南宁', '贵阳', '昆明']
|
|
];
|
|
// 位置搜索数据
|
|
const [locationSearch, setLocationSearch] = useState('');
|
|
// 位置弹窗
|
|
const [locationModalVisible, setLocationModalVisible] = useState(false);
|
|
// 分类弹窗
|
|
const [classifyModalVisible, setClassifyModalVisible] = useState(false);
|
|
// 地区数据
|
|
const [locationData, setLocationData] = useState<CascaderItem[]>([]);
|
|
// 在组件内部添加:
|
|
const podiumRef = useRef<View>(null);
|
|
const [podiumPosition, setPodiumPosition] = useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
// 获取分类
|
|
const [classify, setClassify] = useState<TargetItem[]>([]);
|
|
const getClassify = () => {
|
|
fetchApi<GroupedData>("/title-tags").then((res: GroupedData) => {
|
|
setSelectedClassify([transformData(res)?.[0]?.children?.[0]]);
|
|
setClassify(transformData(res));
|
|
});
|
|
}
|
|
|
|
// 选择地区
|
|
const [selectedLocation, setSelectedLocation] = useState<any>();
|
|
// 选择分类
|
|
const [selectedClassify, setSelectedClassify] = useState<any>();
|
|
|
|
const onPodiumLayout = (event: LayoutChangeEvent) => {
|
|
if (podiumRef.current) {
|
|
podiumRef.current.measure((x, y, width, height, pageX, pageY) => {
|
|
setPodiumPosition({
|
|
x: pageX,
|
|
y: pageY,
|
|
width,
|
|
height
|
|
});
|
|
});
|
|
}
|
|
};
|
|
// 地区选择
|
|
const handleLocationChange = useCallback((selectedItems: CascaderItem[]) => {
|
|
if (selectedItems.length > 0) {
|
|
const lastItem = selectedItems[selectedItems.length - 1];
|
|
// 只有当选择完成时才更新状态
|
|
if (!lastItem.children || lastItem.children.length === 0) {
|
|
setSelectedLocation(selectedItems);
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
// 分类选择
|
|
const handleClassifyChange = useCallback((selectedItems: CascaderItem[]) => {
|
|
if (selectedItems.length > 0) {
|
|
const lastItem = selectedItems[selectedItems.length - 1];
|
|
// 只有当选择完成时才更新状态
|
|
if (!lastItem.children || lastItem.children.length === 0) {
|
|
setSelectedClassify(selectedItems);
|
|
}
|
|
}
|
|
}, []);
|
|
// 获取本地存储的地址信息
|
|
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 getRanking = () => {
|
|
fetchApi<RankingItem[]>("/title-rank", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
"title_tag_id": selectedClassify?.length > 0 ? selectedClassify[selectedClassify?.length - 1].value : null,
|
|
"area_id": selectedLocation?.length > 0 ? selectedLocation[selectedLocation?.length - 1].value : null
|
|
})
|
|
}).then((res) => {
|
|
setRanking(res);
|
|
});
|
|
}
|
|
|
|
// 当用户选择发生变化时,重新获取排名
|
|
useEffect(() => {
|
|
if (selectedLocation?.length > 0 && selectedClassify?.length > 0) {
|
|
getRanking();
|
|
}
|
|
}, [selectedLocation, selectedClassify])
|
|
|
|
// 初始化获取分类
|
|
useEffect(() => {
|
|
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) {
|
|
setSelectedLocation([xuhuiElement]);
|
|
}
|
|
}
|
|
};
|
|
|
|
loadLocationData();
|
|
|
|
return () => {
|
|
isMounted = false;
|
|
};
|
|
}, [fetchLocationData]);
|
|
|
|
useEffect(() => {
|
|
// console.log(locationData);
|
|
|
|
}, [locationSearch])
|
|
|
|
return (
|
|
<TouchableWithoutFeedback onPress={() => {
|
|
Keyboard.dismiss();
|
|
}}>
|
|
<View style={[styles.container, { paddingTop: insets.top }]}>
|
|
{/* 导航栏 */}
|
|
<View
|
|
style={styles.header}
|
|
ref={podiumRef}
|
|
onLayout={onPodiumLayout}
|
|
>
|
|
<TouchableOpacity onPress={() => { router.push('/owner') }} style={{ padding: 16 }}>
|
|
<ReturnArrowSvg />
|
|
</TouchableOpacity>
|
|
<ThemedText style={styles.headerTitle} onPress={() => { setClassifyModalVisible(true) }}>
|
|
{selectedClassify?.length > 0 ? selectedClassify[selectedClassify?.length - 1].name : "分类"}
|
|
</ThemedText>
|
|
<ThemedText className='opacity-0'>123</ThemedText>
|
|
</View>
|
|
<View style={{ display: 'flex', flexDirection: 'column', gap: 16, marginHorizontal: 16, paddingHorizontal: 32 }}>
|
|
<View style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: "space-between", gap: 16 }}>
|
|
<TouchableOpacity onPress={() => { setLocationModalVisible(true) }} style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
<PlaceSvg />
|
|
<ThemedText style={{ color: selectedLocation?.length > 0 ? '#FFB645' : '#4C320C' }}>
|
|
{selectedLocation?.length > 0 ? selectedLocation[selectedLocation?.length - 1].name : "地区"}
|
|
</ThemedText>
|
|
<DownSvg />
|
|
{/* {
|
|
selectedLocation?.length > 0
|
|
?
|
|
<ArrowSvg style={{ transform: [{ rotate: '90deg' }], width: 12, height: 12 }} />
|
|
:
|
|
<ReturnArrowSvg style={{ transform: [{ rotate: '270deg' }], width: 12, height: 12 }} />
|
|
} */}
|
|
|
|
</TouchableOpacity>
|
|
<View style={styles.searchContainer}>
|
|
<View style={styles.searchIcon}>
|
|
<SearchSvg width={12} height={12} />
|
|
</View>
|
|
<TextInput
|
|
style={styles.input}
|
|
onChangeText={setLocationSearch}
|
|
value={locationSearch}
|
|
placeholder="输入城市名进行搜索"
|
|
onEndEditing={(text) => {
|
|
setLocationSearch(text.nativeEvent.text)
|
|
}}
|
|
/>
|
|
</View>
|
|
{/* <TouchableOpacity onPress={() => { setClassifyModalVisible(true) }} style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
<ThemedText style={{ color: selectedClassify?.length > 0 ? '#FFB645' : '#4C320C' }}>
|
|
{selectedClassify?.length > 0 ? selectedClassify[selectedClassify?.length - 1].name : "分类"}
|
|
</ThemedText>
|
|
{selectedClassify?.length > 0
|
|
?
|
|
<ArrowSvg style={{ transform: [{ rotate: '90deg' }], width: 12, height: 12 }} />
|
|
:
|
|
<ReturnArrowSvg style={{ transform: [{ rotate: '270deg' }], width: 12, height: 12 }} />
|
|
}
|
|
</TouchableOpacity> */}
|
|
|
|
</View>
|
|
{/* 热门城市 */}
|
|
<View style={styles.hotCity}>
|
|
<ThemedText size="base" color="textSecondary" style={{ marginBottom: 16 }}>热门城市</ThemedText>
|
|
{HOT_CITIES.map((row, rowIndex) => (
|
|
<View
|
|
key={`row-${rowIndex}`}
|
|
style={[styles.cityRow, rowIndex === HOT_CITIES.length - 1 && { marginBottom: 0 }]}
|
|
>
|
|
{row.map((city, cityIndex) => (
|
|
<ThemedText
|
|
key={`${city}-${cityIndex}`}
|
|
style={styles.item}
|
|
onPress={() => {
|
|
setLocationSearch(city);
|
|
}}
|
|
>
|
|
{city}
|
|
</ThemedText>
|
|
))}
|
|
</View>
|
|
))}
|
|
</View>
|
|
</View>
|
|
|
|
{/* 颁奖台 */}
|
|
<PodiumComponent data={ranking} />
|
|
{/* 排名区域 */}
|
|
<RankList data={ranking} />
|
|
|
|
{/* 地区选择弹窗 */}
|
|
<LocationModal
|
|
modalVisible={locationModalVisible}
|
|
setModalVisible={setLocationModalVisible}
|
|
podiumPosition={podiumPosition}
|
|
handleChange={handleLocationChange}
|
|
data={locationData}
|
|
/>
|
|
{/* 分类选择弹窗 */}
|
|
<ClassifyModal
|
|
data={classify}
|
|
modalVisible={classifyModalVisible}
|
|
setModalVisible={setClassifyModalVisible}
|
|
podiumPosition={podiumPosition}
|
|
handleChange={handleClassifyChange}
|
|
/>
|
|
</View>
|
|
</TouchableWithoutFeedback>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: 'white',
|
|
},
|
|
header: {
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginVertical: 16,
|
|
},
|
|
headerTitle: {
|
|
fontSize: 20,
|
|
fontWeight: '700',
|
|
color: '#4C320C',
|
|
},
|
|
searchContainer: {
|
|
position: 'relative',
|
|
flex: 1,
|
|
},
|
|
input: {
|
|
backgroundColor: '#D9D9D9',
|
|
borderRadius: 24,
|
|
paddingLeft: 40, // 给图标留出空间
|
|
paddingVertical: 8,
|
|
paddingRight: 16,
|
|
fontSize: 14,
|
|
lineHeight: 16,
|
|
},
|
|
searchIcon: {
|
|
position: 'absolute',
|
|
left: 20,
|
|
top: '50%',
|
|
zIndex: 10,
|
|
transform: [{ translateY: -6 }]
|
|
},
|
|
item: {
|
|
paddingVertical: 8,
|
|
borderRadius: 12,
|
|
fontSize: 14,
|
|
backgroundColor: '#D9D9D9',
|
|
width: '23%',
|
|
textAlign: 'center',
|
|
color: "#4C320C"
|
|
},
|
|
hotCity: {
|
|
backgroundColor: "#FBFBFB",
|
|
padding: 16,
|
|
borderRadius: 24
|
|
},
|
|
cityRow: {
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
justifyContent: 'space-between',
|
|
marginBottom: 16
|
|
}
|
|
}); |