轻松聘
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.
 
 
 

493 lines
13 KiB

import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { useRouter } from 'expo-router';
import {
ActivityIndicator,
Pressable,
ScrollView,
StyleSheet,
Text,
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';
const COLOR_TOKENS: Record<string, string> = {
primary: colors.primary,
success: colors.success,
action: colors.action,
info: colors.info,
danger: colors.danger,
};
/* ===================== 子组件:顶部导航 ===================== */
function Header({ onBack, onSettings }: { onBack: () => void; onSettings: () => void }) {
return (
<View style={styles.header}>
<Pressable style={styles.headerBtn} onPress={onBack} hitSlop={8}>
<Ionicons name="chevron-back" size={24} color={colors.textInverse} />
</Pressable>
<Text style={styles.headerTitle}>我的</Text>
<Pressable style={styles.headerBtn} onPress={onSettings} hitSlop={8}>
<Ionicons name="settings-outline" size={22} color={colors.textInverse} />
</Pressable>
</View>
);
}
/* ===================== 子组件:企业信息卡 ===================== */
function CompanyCard({ overview }: { overview: EnterpriseOverview }) {
return (
<View style={styles.companyCard}>
<View style={styles.companyHeader}>
<View style={styles.logoBox}>
<Text style={styles.logoText}>{overview.logo}</Text>
</View>
<View style={styles.companyMeta}>
<Text style={styles.companyName}>{overview.name}</Text>
<Text style={styles.companyTags}>
{overview.industry} · {overview.scale} · {overview.stage}
</Text>
</View>
</View>
<View style={styles.scoreRow}>
<View style={styles.scoreBox}>
<Text style={styles.scoreValue}>{overview.overallScore}</Text>
<Text style={styles.scoreLabel}>综合评分</Text>
</View>
<View style={styles.divider} />
<View style={styles.scoreBox}>
<Text style={styles.scoreValue}>{overview.reviewCount}</Text>
<Text style={styles.scoreLabel}>评价人数</Text>
</View>
<View style={styles.divider} />
<View style={styles.scoreBox}>
<Text style={styles.scoreValue}>{overview.stats[0]?.value ?? 0}</Text>
<Text style={styles.scoreLabel}>在招岗位</Text>
</View>
</View>
</View>
);
}
/* ===================== 子组件:统计四宫格 ===================== */
function StatGrid({ stats }: { stats: EnterpriseStat[] }) {
return (
<View style={styles.card}>
<View style={styles.statGrid}>
{stats.map((stat) => {
const color = COLOR_TOKENS[stat.color] ?? colors.primary;
const up = stat.change >= 0;
return (
<View key={stat.id} style={styles.statItem}>
<View style={[styles.statIcon, { backgroundColor: color + '15' }]}>
<Ionicons name={stat.icon as keyof typeof Ionicons.glyphMap} size={20} color={color} />
</View>
<Text style={styles.statValue}>{stat.value.toLocaleString()}</Text>
<Text style={styles.statLabel}>{stat.label}</Text>
<Text style={[styles.statChange, { color: up ? colors.success : colors.danger }]}>
{up ? '↑' : '↓'} {Math.abs(stat.change)}
</Text>
</View>
);
})}
</View>
</View>
);
}
/* ===================== 子组件:快捷入口 ===================== */
function QuickActions({ actions }: { actions: EnterpriseQuickAction[] }) {
const router = useRouter();
return (
<View style={styles.card}>
<Text style={styles.cardTitle}>快捷入口</Text>
<View style={styles.quickGrid}>
{actions.map((action) => {
const color = COLOR_TOKENS[action.color] ?? colors.primary;
return (
<Pressable
key={action.id}
style={styles.quickItem}
onPress={() => action.route && router.push(action.route as any)}
>
<View style={[styles.quickIcon, { backgroundColor: color + '15' }]}>
<Ionicons name={action.icon as keyof typeof Ionicons.glyphMap} size={22} color={color} />
</View>
<Text style={styles.quickLabel}>{action.label}</Text>
</Pressable>
);
})}
</View>
</View>
);
}
/* ===================== 子组件:岗位评价列表 ===================== */
function JobEvaluationList({ jobs }: { jobs: JobEvaluationSummary[] }) {
const router = useRouter();
return (
<View style={styles.card}>
<View style={styles.cardHeader}>
<Text style={styles.cardTitle}>岗位评价管理</Text>
<Pressable onPress={() => router.push('/job-evaluations')}>
<Text style={styles.cardMore}>查看全部</Text>
</Pressable>
</View>
{jobs.map((job) => {
const up = job.change >= 0;
return (
<View key={job.id} style={styles.jobRow}>
<View style={styles.jobInfo}>
<Text style={styles.jobName}>{job.positionName}</Text>
<Text style={styles.jobDepartment}>{job.department}</Text>
</View>
<View style={styles.jobScoreBox}>
<Text style={styles.jobScore}>{job.score.toFixed(1)}</Text>
<Text style={[styles.jobChange, { color: up ? colors.success : colors.danger }]}>
{up ? '↑' : '↓'} {Math.abs(job.change).toFixed(1)}
</Text>
</View>
</View>
);
})}
</View>
);
}
/* ===================== 子组件:管理后台 ===================== */
/**
* 管理后台入口:运营规则配置 / 数据审计等低频管理操作。
* 单独成区纵向排列,既不挤占快捷入口的横排位,也避免后台术语出现在求职者端。
*/
function ManageList({ actions }: { actions: EnterpriseQuickAction[] }) {
const router = useRouter();
if (actions.length === 0) return null;
return (
<View style={styles.card}>
<Text style={styles.standaloneTitle}>管理后台</Text>
{actions.map((action) => {
const color = COLOR_TOKENS[action.color] ?? colors.primary;
return (
<Pressable
key={action.id}
style={styles.manageRow}
onPress={() => action.route && router.push(action.route as any)}
>
<View style={[styles.manageIcon, { backgroundColor: color + '15' }]}>
<Ionicons name={action.icon as keyof typeof Ionicons.glyphMap} size={20} color={color} />
</View>
<Text style={styles.manageLabel}>{action.label}</Text>
<Ionicons name="chevron-forward" size={18} color={colors.textHint} />
</Pressable>
);
})}
</View>
);
}
/* ===================== 页面主组件 ===================== */
export default function EnterpriseOverviewScreen() {
const router = useRouter();
const { overview, loading, error } = useEnterpriseOverview();
if (loading) {
return (
<SafeAreaView style={styles.loadingContainer} edges={['top']}>
<ActivityIndicator size="large" color={colors.primary} />
</SafeAreaView>
);
}
if (error || !overview) {
return (
<SafeAreaView style={styles.loadingContainer} edges={['top']}>
<Text style={styles.errorText}>加载失败,请稍后重试</Text>
</SafeAreaView>
);
}
return (
<View style={styles.container}>
<LinearGradient colors={headerGradient} style={styles.gradientHeader}>
<SafeAreaView edges={['top']}>
<Header onBack={() => router.back()} onSettings={() => router.push('/settings')} />
<CompanyCard overview={overview} />
</SafeAreaView>
</LinearGradient>
<ScrollView style={styles.scroll} contentContainerStyle={styles.scrollContent}>
<StatGrid stats={overview.stats} />
<QuickActions actions={overview.quickActions} />
<JobEvaluationList jobs={overview.jobEvaluations} />
<ManageList actions={overview.manageActions} />
</ScrollView>
</View>
);
}
/* ===================== 样式 ===================== */
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.pageBg,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.pageBg,
},
errorText: {
color: colors.textSecondary,
fontSize: 14,
},
gradientHeader: {
paddingBottom: 16,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 12,
},
headerBtn: {
width: 36,
height: 36,
justifyContent: 'center',
alignItems: 'center',
},
headerTitle: {
fontSize: 17,
fontWeight: '600',
color: colors.textInverse,
},
companyCard: {
marginHorizontal: 16,
marginTop: 8,
padding: 16,
backgroundColor: colors.white,
borderRadius: 16,
shadowColor: colors.shadow,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.06,
shadowRadius: 8,
elevation: 3,
},
companyHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 16,
},
logoBox: {
width: 52,
height: 52,
borderRadius: 12,
backgroundColor: colors.primaryLight,
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
logoText: {
fontSize: 22,
fontWeight: '700',
color: colors.primary,
},
companyMeta: {
flex: 1,
},
companyName: {
fontSize: 18,
fontWeight: '700',
color: colors.textPrimary,
marginBottom: 4,
},
companyTags: {
fontSize: 13,
color: colors.textSecondary,
},
scoreRow: {
flexDirection: 'row',
alignItems: 'center',
paddingTop: 16,
borderTopWidth: 1,
borderTopColor: colors.borderLight,
},
scoreBox: {
flex: 1,
alignItems: 'center',
},
scoreValue: {
fontSize: 20,
fontWeight: '700',
color: colors.primary,
marginBottom: 2,
},
scoreLabel: {
fontSize: 12,
color: colors.textSecondary,
},
divider: {
width: 1,
height: 32,
backgroundColor: colors.borderLight,
},
scroll: {
flex: 1,
},
scrollContent: {
padding: 16,
paddingBottom: 32,
},
card: {
backgroundColor: colors.white,
borderRadius: 16,
padding: 16,
marginBottom: 16,
shadowColor: colors.shadow,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.04,
shadowRadius: 6,
elevation: 2,
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
cardTitle: {
fontSize: 16,
fontWeight: '700',
color: colors.textPrimary,
},
cardMore: {
fontSize: 13,
color: colors.primary,
},
statGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
marginHorizontal: -6,
},
statItem: {
width: '25%',
paddingHorizontal: 6,
alignItems: 'center',
},
statIcon: {
width: 44,
height: 44,
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 8,
},
statValue: {
fontSize: 17,
fontWeight: '700',
color: colors.textPrimary,
marginBottom: 2,
},
statLabel: {
fontSize: 12,
color: colors.textSecondary,
marginBottom: 2,
},
statChange: {
fontSize: 11,
fontWeight: '500',
},
quickGrid: {
flexDirection: 'row',
marginHorizontal: -8,
},
quickItem: {
flex: 1,
alignItems: 'center',
paddingVertical: 8,
},
quickIcon: {
width: 48,
height: 48,
borderRadius: 14,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 8,
},
quickLabel: {
fontSize: 13,
color: colors.textPrimary,
},
jobRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
jobInfo: {
flex: 1,
},
jobName: {
fontSize: 15,
fontWeight: '600',
color: colors.textPrimary,
marginBottom: 2,
},
jobDepartment: {
fontSize: 12,
color: colors.textSecondary,
},
jobScoreBox: {
alignItems: 'flex-end',
},
jobScore: {
fontSize: 18,
fontWeight: '700',
color: colors.primary,
},
jobChange: {
fontSize: 11,
fontWeight: '500',
marginTop: 2,
},
standaloneTitle: {
fontSize: 16,
fontWeight: '700',
color: colors.textPrimary,
marginBottom: 8,
},
manageRow: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
manageIcon: {
width: 38,
height: 38,
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
manageLabel: {
flex: 1,
fontSize: 15,
fontWeight: '500',
color: colors.textPrimary,
},
});