轻松聘
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

213 lines
6.1 KiB

import { Ionicons } from '@expo/vector-icons';
import { Tabs } from 'expo-router/js-tabs';
import type { BottomTabBarProps } from 'expo-router/js-tabs';
import { useFocusEffect } from 'expo-router';
import { useCallback, useEffect, useRef } from 'react';
import { Animated, Easing, Pressable, StyleSheet, View } from 'react-native';
import { colors } from '../../src/theme';
import { useRole } from '../../src/context/RoleContext';
/** 企业端默认落地「岗位」Tab(与求职端首屏一致) */
export const unstable_settings = {
initialRouteName: 'job-management',
};
/** 企业端底部 Tab 与求职端共用品牌主色 primary;未选中 -outline,选中填充实心 */
const ICON_MAP = {
'enterprise-overview': { outline: 'person-outline', filled: 'person' },
'job-management': { outline: 'folder-outline', filled: 'folder' },
'talent-recommendation': { outline: 'people-outline', filled: 'people' },
'enterprise-chat': { outline: 'chatbubble-outline', filled: 'chatbubble' },
} as const;
type TabRoute = keyof typeof ICON_MAP;
function TabItem({
focused,
route,
label,
onPress,
onLongPress,
}: {
focused: boolean;
route: TabRoute;
label: string;
onPress: () => void;
onLongPress: () => void;
}) {
const { outline, filled } = ICON_MAP[route];
const translateY = useRef(new Animated.Value(focused ? -2 : 0)).current;
const progress = useRef(new Animated.Value(focused ? 1 : 0)).current;
useEffect(() => {
const translateAnim = Animated.timing(translateY, {
toValue: focused ? -2 : 0,
duration: 150,
easing: Easing.out(Easing.ease),
useNativeDriver: true,
});
const progressAnim = Animated.timing(progress, {
toValue: focused ? 1 : 0,
duration: 200,
easing: Easing.inOut(Easing.ease),
useNativeDriver: true,
});
translateAnim.start();
progressAnim.start();
return () => {
translateAnim.stop();
progressAnim.stop();
};
}, [focused, translateY, progress]);
const inactiveOpacity = progress.interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
});
return (
<Pressable
style={({ pressed }) => [styles.tabItem, pressed && styles.tabItemPressed]}
onPress={onPress}
onLongPress={onLongPress}
accessibilityRole="button"
accessibilityState={{ selected: focused }}
>
<Animated.View style={{ transform: [{ translateY }] }}>
<View style={styles.iconBox}>
<Animated.View
style={[StyleSheet.absoluteFill, styles.center, { opacity: inactiveOpacity }]}
>
<Ionicons name={outline} size={22} color={colors.textHint} />
</Animated.View>
<Animated.View style={[StyleSheet.absoluteFill, styles.center, { opacity: progress }]}>
<Ionicons name={filled} size={22} color={colors.primary} />
</Animated.View>
</View>
</Animated.View>
<View style={styles.labelBox}>
<Animated.Text style={[styles.tabLabel, styles.labelLayer, { opacity: inactiveOpacity }]}>
{label}
</Animated.Text>
<Animated.Text
style={[styles.tabLabel, styles.tabLabelActive, styles.labelLayer, { opacity: progress }]}
>
{label}
</Animated.Text>
</View>
</Pressable>
);
}
function TabBar({ state, descriptors, navigation, insets }: BottomTabBarProps) {
return (
<View style={[styles.tabBar, { paddingBottom: insets.bottom + 4 }]}>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const focused = state.index === index;
const label =
typeof options.tabBarLabel === 'string' ? options.tabBarLabel : options.title ?? '';
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!focused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};
const onLongPress = () => navigation.emit({ type: 'tabLongPress', target: route.key });
return (
<TabItem
key={route.key}
route={route.name as TabRoute}
focused={focused}
label={label}
onPress={onPress}
onLongPress={onLongPress}
/>
);
})}
</View>
);
}
/**
* 企业端底部 Tab 导航:岗位 / 人才 / 沟通 / 我的
* 绿色 success 强调,与求职端蓝色 primary 形成视觉区分。
*/
export default function EnterpriseTabLayout() {
const { setRole } = useRole();
// 用 useFocusEffect:进入/返回该端时随焦点写入角色,
// 避免「切换到企业端」后返回时 layout 未重新挂载导致 role 残留 candidate。
useFocusEffect(
useCallback(() => {
setRole('enterprise');
}, [setRole]),
);
return (
<Tabs
screenOptions={{ headerShown: false }}
tabBar={(props) => <TabBar {...props} />}
>
<Tabs.Screen name="job-management" options={{ title: '岗位' }} />
<Tabs.Screen name="talent-recommendation" options={{ title: '人才' }} />
<Tabs.Screen name="enterprise-chat" options={{ title: '沟通' }} />
<Tabs.Screen name="enterprise-overview" options={{ title: '我的' }} />
</Tabs>
);
}
const styles = StyleSheet.create({
tabBar: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.white,
paddingTop: 4,
},
tabItem: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 2,
},
tabItemPressed: {
transform: [{ scale: 0.92 }],
},
iconBox: {
width: 22,
height: 22,
},
center: {
alignItems: 'center',
justifyContent: 'center',
},
labelBox: {
height: 14,
marginTop: 1,
alignSelf: 'stretch',
},
labelLayer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
},
tabLabel: {
fontSize: 11,
lineHeight: 14,
fontWeight: '400',
textAlign: 'center',
color: colors.textHint,
},
tabLabelActive: {
fontWeight: '600',
color: colors.primary,
},
});