import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, TextInput, 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'; /* ===================== 子组件:顶部导航 ===================== */ function Header({ companyName, onBack }: { companyName: string; onBack: () => void }) { const router = useRouter(); return ( {companyName} 沟通 router.navigate('/talent-recommendation')} hitSlop={6} > 发起沟通 ); } /* ===================== 子组件:Tab 栏 ===================== */ function TabBar({ tabs, active, onChange, }: { tabs: EnterpriseCommunicationTab[]; active: string; onChange: (key: string) => void; }) { return ( {tabs.map((tab) => { const isActive = tab.key === active; return ( onChange(tab.key)}> {tab.label} ({tab.count}) ); })} ); } /* ===================== 子组件:待处理任务 ===================== */ function PendingTasks({ tasks, onPressTask, }: { tasks: PendingTask[]; onPressTask: (task: PendingTask) => void; }) { return ( 待处理 {tasks.map((task) => { const token = task.color === 'action' ? colors.action : task.color === 'compareOrange' ? colors.compareOrange : colors.primary; return ( onPressTask(task)} > {task.label} {task.count}人 {task.hint ? {task.hint} : null} ); })} ); } /* ===================== 子组件:聊天列表项 ===================== */ function ChatRow({ chat }: { chat: EnterpriseChatItem }) { const router = useRouter(); return ( router.push('/chat/' + chat.id)}> {chat.avatar} {chat.name} {chat.labels.length > 0 ? ( {chat.labels[0]} ) : null} {chat.time} {chat.title} {chat.lastMessage} {chat.unreadCount > 0 ? ( {chat.unreadCount} ) : null} ); } /* ===================== 子组件:搜索栏 ===================== */ function SearchBar({ value, onChange, onClear, }: { value: string; onChange: (text: string) => void; onClear: () => void; }) { return ( {value !== '' && ( )} ); } /* ===================== 子组件:快捷操作栏 ===================== */ /** 单个快捷操作。`route` 必填 —— 每个入口都必须有落点,没有目标页的宁可不放 */ type QuickActionItem = { icon: string; label: string; route: string }; function QuickActions({ actions, onPressAction, }: { actions: QuickActionItem[]; onPressAction: (action: QuickActionItem) => void; }) { return ( {actions.map((action) => ( onPressAction(action)}> {action.label} ))} ); } /* ===================== 子组件:空状态 ===================== */ function EmptyState({ dataReference, onDiscover, }: { dataReference: { label: string; value: string }[]; onDiscover: () => void; }) { return ( 还没有沟通记录 从人才推荐中发现合适的候选人,主动发起沟通吧 去发现人才 数据参考 {dataReference.map((item, index) => ( {item.label} {item.value} ))} ); } /* ===================== 页面 ===================== */ /* * 「批量发Offer」「群发通知」原先在这里,但两者都没有对应页面,点了只能弹「功能开发中」。 * 按「不留假交互」的口径摘掉(与 talent-recommendation.tsx 的 POOL_ACTIONS 同一处理)。 */ const QUICK_ACTIONS: QuickActionItem[] = [ { icon: 'mail-outline', label: '批量发邀请', route: '/talent-recommendation' }, { icon: 'calendar-outline', label: '面试评分', route: '/interview-score' }, ]; export default function EnterpriseChatScreen() { const router = useRouter(); const { communication, activeTab, setActiveTab, keyword, setKeyword, chats, isEmptySearch, loading, error, refresh, } = useEnterpriseCommunication(); /** 快捷操作:每个入口都由数据保证有落点,页面不再需要「没页面就弹提示」的分支 */ const handleQuickAction = (action: QuickActionItem) => { router.push(action.route); }; // 待办卡片:跳页面或切 Tab,目标由接口层给,页面不写死映射。 // PendingTask 是联合类型,两者必居其一,故不需要「都没有」的兜底分支 const handlePressTask = (task: PendingTask) => { if (task.tabKey) { setActiveTab(task.tabKey); setKeyword(''); return; } router.push(task.route as never); }; if (loading) { return (
router.back()} /> ); } if (error || !communication) { return (
router.back()} /> 加载失败 重试 ); } return (
router.back()} /> {communication.hasData ? ( setKeyword('')} /> {chats.length === 0 ? ( {isEmptySearch ? `没有匹配「${keyword.trim()}」的会话` : '这个分类下暂时没有会话'} ) : ( {chats.map((chat, index) => { const isLast = index === chats.length - 1; return ( ); })} )} ) : ( router.navigate('/talent-recommendation')} /> )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.pageBg }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 12, paddingVertical: 12, backgroundColor: colors.white, }, headerBtn: { width: 40, alignItems: 'center' }, headerCenter: { alignItems: 'center' }, headerCompany: { fontSize: 12, color: colors.textSecondary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary, marginTop: 2 }, startBtn: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.primaryBtn, paddingHorizontal: 10, paddingVertical: 6, borderRadius: 16, }, startBtnText: { color: colors.white, fontSize: 12, fontWeight: '600', marginLeft: 2, }, tabBar: { flexDirection: 'row', backgroundColor: colors.white, borderBottomWidth: 1, borderBottomColor: colors.borderLight, }, tabItem: { flex: 1, alignItems: 'center', paddingTop: 8, }, tabLabel: { fontSize: 13, color: colors.textSecondary, paddingBottom: 8, }, tabLabelActive: { color: colors.textPrimary, fontWeight: '600', }, tabCount: { fontSize: 12, color: colors.textHint, marginLeft: 2, }, tabCountActive: { color: colors.primary, }, tabIndicator: { height: 2, width: 20, borderRadius: 1, backgroundColor: 'transparent', marginBottom: -1, }, tabIndicatorActive: { backgroundColor: colors.primary, }, body: { padding: 16, paddingBottom: 24, }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12, }, tip: { fontSize: 14, color: colors.textSecondary }, retryBtn: { marginTop: 8, paddingHorizontal: 24, paddingVertical: 8, borderRadius: 18, backgroundColor: colors.primaryBtn, }, retryText: { color: colors.white, fontSize: 13, fontWeight: '600' }, pendingCard: { backgroundColor: colors.white, borderRadius: 12, padding: 16, marginBottom: 12, }, pendingTitle: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, marginBottom: 12, }, pendingGrid: { gap: 12, }, pendingItem: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.pageBg, borderRadius: 10, padding: 12, }, pendingIconWrap: { width: 40, height: 40, borderRadius: 10, alignItems: 'center', justifyContent: 'center', marginRight: 12, }, pendingBody: { flex: 1, }, pendingLabelRow: { flexDirection: 'row', alignItems: 'center', }, pendingLabel: { fontSize: 14, fontWeight: '600', color: colors.textPrimary, }, pendingBadge: { backgroundColor: colors.actionLight, borderRadius: 10, paddingHorizontal: 8, paddingVertical: 2, marginLeft: 8, }, pendingBadgeText: { fontSize: 12, fontWeight: '700', color: colors.actionText, }, pendingHint: { fontSize: 12, color: colors.textSecondary, marginTop: 2, }, searchBar: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.white, borderRadius: 10, paddingHorizontal: 12, paddingVertical: 10, marginBottom: 12, }, searchInput: { flex: 1, marginLeft: 8, fontSize: 14, color: colors.textPrimary, padding: 0, }, emptyList: { alignItems: 'center', gap: 10, paddingVertical: 32, backgroundColor: colors.white, borderRadius: 14, }, emptyListText: { fontSize: 13, color: colors.textSecondary }, chatListCard: { backgroundColor: colors.white, borderRadius: 12, overflow: 'hidden', marginBottom: 12, }, chatBorder: { borderBottomWidth: 1, borderBottomColor: colors.borderLight, }, chatRow: { flexDirection: 'row', alignItems: 'center', padding: 14, }, chatAvatar: { width: 48, height: 48, borderRadius: 24, backgroundColor: colors.primaryLight, alignItems: 'center', justifyContent: 'center', marginRight: 12, }, chatAvatarText: { fontSize: 18, fontWeight: '700', color: colors.primary, }, chatBody: { flex: 1, }, chatHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, chatNameWrap: { flexDirection: 'row', alignItems: 'center', }, chatName: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, }, chatLabelTag: { backgroundColor: colors.compareOrange + '20', borderRadius: 8, paddingHorizontal: 6, paddingVertical: 2, marginLeft: 8, }, chatLabelTagText: { fontSize: 11, color: colors.compareOrange, fontWeight: '600', }, chatTime: { fontSize: 12, color: colors.textHint, }, chatTitle: { fontSize: 13, color: colors.textSecondary, marginTop: 2, }, chatFooter: { flexDirection: 'row', alignItems: 'center', marginTop: 4, }, chatLastMessage: { flex: 1, fontSize: 13, color: colors.textHint, }, unreadBadge: { minWidth: 18, height: 18, borderRadius: 9, backgroundColor: colors.danger, alignItems: 'center', justifyContent: 'center', marginLeft: 8, }, unreadBadgeText: { fontSize: 11, color: colors.white, fontWeight: '700', }, quickActionsCard: { flexDirection: 'row', backgroundColor: colors.white, borderRadius: 12, paddingVertical: 14, }, quickAction: { flex: 1, alignItems: 'center', }, quickActionText: { fontSize: 11, color: colors.textSecondary, marginTop: 4, }, emptyCard: { backgroundColor: colors.white, borderRadius: 16, padding: 28, alignItems: 'center', }, emptyIconWrap: { width: 88, height: 88, borderRadius: 44, backgroundColor: colors.primaryLight, alignItems: 'center', justifyContent: 'center', marginBottom: 16, }, emptyTitle: { fontSize: 18, fontWeight: '700', color: colors.textPrimary, marginBottom: 8, }, emptySubtitle: { fontSize: 13, color: colors.textSecondary, textAlign: 'center', marginBottom: 20, }, emptyBtn: { backgroundColor: colors.primaryBtn, paddingHorizontal: 32, paddingVertical: 12, borderRadius: 24, marginBottom: 24, }, emptyBtnText: { color: colors.white, fontSize: 15, fontWeight: '600', }, dataCard: { width: '100%', backgroundColor: colors.primaryLight, borderRadius: 12, padding: 16, alignItems: 'flex-start', }, dataTitle: { fontSize: 14, fontWeight: '700', color: colors.primaryText, marginBottom: 12, }, dataRow: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: 10, }, dataText: { flex: 1, fontSize: 13, color: colors.textPrimary, marginLeft: 10, lineHeight: 18, }, dataValue: { color: colors.success, fontWeight: '700', }, });