diff --git a/app/(enterprise)/_layout.tsx b/app/(enterprise)/_layout.tsx
new file mode 100644
index 0000000..c159353
--- /dev/null
+++ b/app/(enterprise)/_layout.tsx
@@ -0,0 +1,208 @@
+import { Ionicons } from '@expo/vector-icons';
+import { Tabs } from 'expo-router/js-tabs';
+import type { BottomTabBarProps } from 'expo-router/js-tabs';
+import { 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: 'enterprise-overview',
+};
+
+/** 企业端用 success(绿) 区分求职端 primary(蓝);未选中 -outline,选中填充实心 */
+const ICON_MAP = {
+ 'enterprise-overview': { outline: 'grid-outline', filled: 'grid' },
+ '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 (
+ [styles.tabItem, pressed && styles.tabItemPressed]}
+ onPress={onPress}
+ onLongPress={onLongPress}
+ accessibilityRole="button"
+ accessibilityState={{ selected: focused }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {label}
+
+
+ {label}
+
+
+
+ );
+}
+
+function TabBar({ state, descriptors, navigation, insets }: BottomTabBarProps) {
+ return (
+
+ {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 (
+
+ );
+ })}
+
+ );
+}
+
+/**
+ * 企业端底部 Tab 导航:概览 / 岗位 / 人才 / 沟通
+ * 绿色 success 强调,与求职端蓝色 primary 形成视觉区分。
+ */
+export default function EnterpriseTabLayout() {
+ const { setRole } = useRole();
+
+ useEffect(() => {
+ setRole('enterprise');
+ }, [setRole]);
+
+ return (
+ }
+ >
+
+
+
+
+
+ );
+}
+
+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.success,
+ },
+});
diff --git a/app/audit-report.tsx b/app/(enterprise)/audit-report.tsx
similarity index 99%
rename from app/audit-report.tsx
rename to app/(enterprise)/audit-report.tsx
index 0edf664..d95434c 100644
--- a/app/audit-report.tsx
+++ b/app/(enterprise)/audit-report.tsx
@@ -3,9 +3,9 @@ import { useRouter } from 'expo-router';
import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Line, Path, Rect, Text as SvgText } from 'react-native-svg';
-import { useAuditReport } from '../src/hooks';
-import { colors } from '../src/theme';
-import type { AuditKPI, AuditTrendPoint, RiskDistributionItem } from '../src/types';
+import { useAuditReport } from '../../src/hooks';
+import { colors } from '../../src/theme';
+import type { AuditKPI, AuditTrendPoint, RiskDistributionItem } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */
diff --git a/app/candidate-detail.tsx b/app/(enterprise)/candidate-detail.tsx
similarity index 99%
rename from app/candidate-detail.tsx
rename to app/(enterprise)/candidate-detail.tsx
index 2d7e302..a3c7887 100644
--- a/app/candidate-detail.tsx
+++ b/app/(enterprise)/candidate-detail.tsx
@@ -10,9 +10,9 @@ import {
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Line, Path, Polygon, Text as SvgText } from 'react-native-svg';
-import { useCandidateDetail } from '../src/hooks';
-import { colors } from '../src/theme';
-import type { CandidateAbility, CandidateReview } from '../src/types';
+import { useCandidateDetail } from '../../src/hooks';
+import { colors } from '../../src/theme';
+import type { CandidateAbility, CandidateReview } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */
diff --git a/app/enterprise-certification.tsx b/app/(enterprise)/enterprise-certification.tsx
similarity index 98%
rename from app/enterprise-certification.tsx
rename to app/(enterprise)/enterprise-certification.tsx
index 68885e8..6e96c69 100644
--- a/app/enterprise-certification.tsx
+++ b/app/(enterprise)/enterprise-certification.tsx
@@ -11,9 +11,9 @@ import {
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
-import { useEnterpriseCertification } from '../src/hooks';
-import { colors } from '../src/theme';
-import type { EnterpriseCertificationForm, EnterpriseCertificationStatus } from '../src/types';
+import { useEnterpriseCertification } from '../../src/hooks';
+import { colors } from '../../src/theme';
+import type { EnterpriseCertificationForm, EnterpriseCertificationStatus } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */
diff --git a/app/enterprise-chat.tsx b/app/(enterprise)/enterprise-chat.tsx
similarity index 99%
rename from app/enterprise-chat.tsx
rename to app/(enterprise)/enterprise-chat.tsx
index e60eb1c..4b43016 100644
--- a/app/enterprise-chat.tsx
+++ b/app/(enterprise)/enterprise-chat.tsx
@@ -10,9 +10,9 @@ import {
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
-import { useEnterpriseCommunication } from '../src/hooks';
-import { colors } from '../src/theme';
-import type { EnterpriseChatItem, EnterpriseCommunicationTab, PendingTask } from '../src/types';
+import { useEnterpriseCommunication } from '../../src/hooks';
+import { colors } from '../../src/theme';
+import type { EnterpriseChatItem, EnterpriseCommunicationTab, PendingTask } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */
diff --git a/app/enterprise-overview.tsx b/app/(enterprise)/enterprise-overview.tsx
similarity index 98%
rename from app/enterprise-overview.tsx
rename to app/(enterprise)/enterprise-overview.tsx
index 30d22a0..05a2c65 100644
--- a/app/enterprise-overview.tsx
+++ b/app/(enterprise)/enterprise-overview.tsx
@@ -10,9 +10,9 @@ import {
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
-import { useEnterpriseOverview } from '../src/hooks';
-import { colors, headerGradient } from '../src/theme';
-import type { EnterpriseOverview, EnterpriseQuickAction, EnterpriseStat, JobEvaluationSummary } from '../src/types';
+import { useEnterpriseOverview } from '../../src/hooks';
+import { colors, headerGradient } from '../../src/theme';
+import type { EnterpriseOverview, EnterpriseQuickAction, EnterpriseStat, JobEvaluationSummary } from '../../src/types';
const COLOR_TOKENS: Record = {
primary: colors.primary,
diff --git a/app/hr-evaluation.tsx b/app/(enterprise)/hr-evaluation.tsx
similarity index 99%
rename from app/hr-evaluation.tsx
rename to app/(enterprise)/hr-evaluation.tsx
index ba15b4e..12f810f 100644
--- a/app/hr-evaluation.tsx
+++ b/app/(enterprise)/hr-evaluation.tsx
@@ -10,9 +10,9 @@ import {
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Circle, Line, Path, Text as SvgText } from 'react-native-svg';
-import { useHREvaluation } from '../src/hooks';
-import { colors } from '../src/theme';
-import type { EvaluationDimension, EvaluationReview, EvaluationTrendPoint } from '../src/types';
+import { useHREvaluation } from '../../src/hooks';
+import { colors } from '../../src/theme';
+import type { EvaluationDimension, EvaluationReview, EvaluationTrendPoint } from '../../src/types';
/** 评级 → 文字颜色(优秀=品牌蓝,良好=主色,其他=次要文字) */
const LEVEL_TEXT_COLOR: Record = {
diff --git a/app/job-management.tsx b/app/(enterprise)/job-management.tsx
similarity index 96%
rename from app/job-management.tsx
rename to app/(enterprise)/job-management.tsx
index 86d93d6..4327d81 100644
--- a/app/job-management.tsx
+++ b/app/(enterprise)/job-management.tsx
@@ -11,9 +11,9 @@ import {
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
-import { useJobManagement } from '../src/hooks';
-import { colors } from '../src/theme';
-import type { JobManagement, JobManagementJob, JobManagementTab } from '../src/types';
+import { useJobManagement } from '../../src/hooks';
+import { colors } from '../../src/theme';
+import type { JobManagement, JobManagementJob, JobManagementTab } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */
@@ -117,11 +117,11 @@ function SearchFilterBar() {
placeholderTextColor={colors.textHint}
/>
- console.log('筛选')}>
+ {}}>
筛选
- console.log('最新发布')}>
+ {}}>
最新发布
@@ -178,15 +178,15 @@ function JobCard({ job }: { job: JobManagementJob }) {
- console.log('编辑', job.id)}>
+ {}}>
编辑
- console.log('刷新', job.id)}>
+ {}}>
刷新
- console.log('下架', job.id)}>
+ {}}>
下架
diff --git a/app/job-publish.tsx b/app/(enterprise)/job-publish.tsx
similarity index 99%
rename from app/job-publish.tsx
rename to app/(enterprise)/job-publish.tsx
index c47bbe6..496f419 100644
--- a/app/job-publish.tsx
+++ b/app/(enterprise)/job-publish.tsx
@@ -11,9 +11,9 @@ import {
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
-import { useJobPublish } from '../src/hooks';
-import { colors } from '../src/theme';
-import type { JobPublishFormData } from '../src/types';
+import { useJobPublish } from '../../src/hooks';
+import { colors } from '../../src/theme';
+import type { JobPublishFormData } from '../../src/types';
const WORK_TYPE_OPTIONS: { label: string; value: JobPublishFormData['workType'] }[] = [
{ label: '全职', value: 'full-time' },
diff --git a/app/operation-rules.tsx b/app/(enterprise)/operation-rules.tsx
similarity index 98%
rename from app/operation-rules.tsx
rename to app/(enterprise)/operation-rules.tsx
index b3435d7..1336cca 100644
--- a/app/operation-rules.tsx
+++ b/app/(enterprise)/operation-rules.tsx
@@ -12,9 +12,9 @@ import {
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
-import { useOperationRules } from '../src/hooks';
-import { colors } from '../src/theme';
-import type { MatchMode, OperationRules } from '../src/types';
+import { useOperationRules } from '../../src/hooks';
+import { colors } from '../../src/theme';
+import type { MatchMode, OperationRules } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */
diff --git a/app/talent-recommendation.tsx b/app/(enterprise)/talent-recommendation.tsx
similarity index 98%
rename from app/talent-recommendation.tsx
rename to app/(enterprise)/talent-recommendation.tsx
index 5b460c3..e8e7416 100644
--- a/app/talent-recommendation.tsx
+++ b/app/(enterprise)/talent-recommendation.tsx
@@ -10,9 +10,9 @@ import {
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
-import { useTalentRecommendation } from '../src/hooks';
-import { colors } from '../src/theme';
-import type { TalentCandidate, TalentRecommendationTab } from '../src/types';
+import { useTalentRecommendation } from '../../src/hooks';
+import { colors } from '../../src/theme';
+import type { TalentCandidate, TalentRecommendationTab } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */
@@ -264,7 +264,7 @@ export default function TalentRecommendationScreen() {
console.log('[TalentRecommendation] 查看人才市场报告')}
+ onReport={() => {}}
/>
{recommendation.candidates.map((candidate) => (
@@ -275,7 +275,7 @@ export default function TalentRecommendationScreen() {
console.log('[TalentRecommendation] 调整筛选条件')}
+ onFilter={() => {}}
/>
)}
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index 4f39c5f..2bbb289 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -4,6 +4,7 @@ import type { BottomTabBarProps } from 'expo-router/js-tabs';
import { useEffect, useRef } from 'react';
import { Animated, Easing, Pressable, StyleSheet, View } from 'react-native';
import { colors } from '../../src/theme';
+import { useRole } from '../../src/context/RoleContext';
/** 无 index 路由,默认进入「岗位」Tab 作为落地页 */
export const unstable_settings = {
@@ -161,6 +162,12 @@ function TabBar({ state, descriptors, navigation, insets }: BottomTabBarProps) {
* 底部 Tab 导航:4 个 Tab(岗位/沟通/成长/我的),高级感极简风格
*/
export default function TabLayout() {
+ const { setRole } = useRole();
+
+ useEffect(() => {
+ setRole('candidate');
+ }, [setRole]);
+
return (
+
+
@@ -17,6 +19,6 @@ export default function RootLayout() {
- >
+
);
}
\ No newline at end of file
diff --git a/app/identity-select.tsx b/app/identity-select.tsx
index f2c69b4..b2abf25 100644
--- a/app/identity-select.tsx
+++ b/app/identity-select.tsx
@@ -11,6 +11,7 @@ import {
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useIdentityOptions } from '../src/hooks';
+import { useRole } from '../src/context/RoleContext';
import { colors } from '../src/theme';
import type { IdentityOption } from '../src/types';
@@ -97,15 +98,18 @@ function ErrorState({ onRetry }: { onRetry: () => void }) {
export default function IdentitySelectScreen() {
const router = useRouter();
+ const { setRole } = useRole();
const { options, loading, error, refresh } = useIdentityOptions();
const [selectedId, setSelectedId] = useState(null);
const handleCandidatePress = () => {
+ setRole('candidate');
router.replace('/(tabs)');
};
const handleEnterprisePress = () => {
- router.push('/enterprise-overview');
+ setRole('enterprise');
+ router.replace('/(enterprise)');
};
return (
diff --git a/src/context/RoleContext.tsx b/src/context/RoleContext.tsx
new file mode 100644
index 0000000..c8da690
--- /dev/null
+++ b/src/context/RoleContext.tsx
@@ -0,0 +1,77 @@
+import {
+ createContext,
+ useContext,
+ useEffect,
+ useState,
+ type ReactNode,
+} from 'react';
+
+/* ===================== 角色(端)上下文 =====================
+ * 区分「求职端 candidate」与「企业端 enterprise」两套管路。
+ * - 进入 (tabs) 视为求职端,进入 (enterprise) 视为企业端;
+ * - identity-select 选定身份时写入 role,两端布局挂载时也会兜底校正。
+ *
+ * 持久化:当前为会话内(内存)实现,App 重启后回到 null(重新选择身份)。
+ * 若已安装 @react-native-async-storage/async-storage,可将文件底部
+ * persistRole / loadRole 替换为真实读写,即可跨重启记住当前端。
+ */
+
+export type AppRole = 'candidate' | 'enterprise';
+
+interface RoleContextValue {
+ role: AppRole | null;
+ setRole: (role: AppRole) => void;
+ clearRole: () => void;
+}
+
+const RoleContext = createContext(undefined);
+
+let memoryRole: AppRole | null = null;
+
+function persistRole(role: AppRole | null): void {
+ memoryRole = role;
+ // TODO(持久化): 安装 async-storage 后改为 AsyncStorage.setItem('app_role', role ?? '')
+}
+
+function loadRole(): Promise {
+ return Promise.resolve(memoryRole);
+ // TODO(持久化): 安装 async-storage 后改为 AsyncStorage.getItem('app_role')
+}
+
+export function RoleProvider({ children }: { children: ReactNode }) {
+ const [role, setRoleState] = useState(null);
+
+ useEffect(() => {
+ let active = true;
+ loadRole().then((stored) => {
+ if (active && stored) setRoleState(stored);
+ });
+ return () => {
+ active = false;
+ };
+ }, []);
+
+ const setRole = (next: AppRole) => {
+ setRoleState(next);
+ persistRole(next);
+ };
+
+ const clearRole = () => {
+ setRoleState(null);
+ persistRole(null);
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useRole(): RoleContextValue {
+ const ctx = useContext(RoleContext);
+ if (!ctx) {
+ throw new Error('useRole 必须在 RoleProvider 内部使用');
+ }
+ return ctx;
+}