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.
610 lines
16 KiB
610 lines
16 KiB
import { Ionicons } from '@expo/vector-icons';
|
|
import { useRouter } from 'expo-router';
|
|
import { useState } from 'react';
|
|
import {
|
|
ActivityIndicator,
|
|
Pressable,
|
|
ScrollView,
|
|
StyleSheet,
|
|
Text,
|
|
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 (
|
|
<View style={styles.header}>
|
|
<Pressable style={styles.headerBtn} onPress={onBack} hitSlop={8}>
|
|
<Ionicons name="chevron-back" size={24} color={colors.textPrimary} />
|
|
</Pressable>
|
|
<View style={styles.headerCenter}>
|
|
<Text style={styles.headerCompany}>{companyName}</Text>
|
|
<Text style={styles.headerTitle}>沟通</Text>
|
|
</View>
|
|
<Pressable
|
|
style={styles.startBtn}
|
|
onPress={() => router.push('/talent-recommendation')}
|
|
hitSlop={6}
|
|
>
|
|
<Ionicons name="add" size={16} color={colors.white} />
|
|
<Text style={styles.startBtnText}>发起沟通</Text>
|
|
</Pressable>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 子组件:Tab 栏 ===================== */
|
|
|
|
function TabBar({
|
|
tabs,
|
|
active,
|
|
onChange,
|
|
}: {
|
|
tabs: EnterpriseCommunicationTab[];
|
|
active: string;
|
|
onChange: (key: string) => void;
|
|
}) {
|
|
return (
|
|
<View style={styles.tabBar}>
|
|
{tabs.map((tab) => {
|
|
const isActive = tab.key === active;
|
|
return (
|
|
<Pressable key={tab.key} style={styles.tabItem} onPress={() => onChange(tab.key)}>
|
|
<Text style={[styles.tabLabel, isActive && styles.tabLabelActive]}>
|
|
{tab.label}
|
|
<Text style={[styles.tabCount, isActive && styles.tabCountActive]}>({tab.count})</Text>
|
|
</Text>
|
|
<View style={[styles.tabIndicator, isActive && styles.tabIndicatorActive]} />
|
|
</Pressable>
|
|
);
|
|
})}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 子组件:待处理任务 ===================== */
|
|
|
|
function PendingTasks({ tasks }: { tasks: PendingTask[] }) {
|
|
return (
|
|
<View style={styles.pendingCard}>
|
|
<Text style={styles.pendingTitle}>待处理</Text>
|
|
<View style={styles.pendingGrid}>
|
|
{tasks.map((task) => {
|
|
const token =
|
|
task.color === 'action'
|
|
? colors.action
|
|
: task.color === 'compareOrange'
|
|
? colors.compareOrange
|
|
: colors.primary;
|
|
return (
|
|
<Pressable key={task.id} style={styles.pendingItem}>
|
|
<View style={[styles.pendingIconWrap, { backgroundColor: token + '15' }]}>
|
|
<Ionicons name={task.icon as keyof typeof Ionicons.glyphMap} size={22} color={token} />
|
|
</View>
|
|
<View style={styles.pendingBody}>
|
|
<View style={styles.pendingLabelRow}>
|
|
<Text style={styles.pendingLabel}>{task.label}</Text>
|
|
<View style={styles.pendingBadge}>
|
|
<Text style={styles.pendingBadgeText}>{task.count}人</Text>
|
|
</View>
|
|
</View>
|
|
{task.hint ? <Text style={styles.pendingHint}>{task.hint}</Text> : null}
|
|
</View>
|
|
<Ionicons name="chevron-forward" size={16} color={colors.textHint} />
|
|
</Pressable>
|
|
);
|
|
})}
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 子组件:聊天列表项 ===================== */
|
|
|
|
function ChatRow({ chat }: { chat: EnterpriseChatItem }) {
|
|
return (
|
|
<Pressable style={styles.chatRow}>
|
|
<View style={styles.chatAvatar}>
|
|
<Text style={styles.chatAvatarText}>{chat.avatar}</Text>
|
|
</View>
|
|
<View style={styles.chatBody}>
|
|
<View style={styles.chatHeader}>
|
|
<View style={styles.chatNameWrap}>
|
|
<Text style={styles.chatName}>{chat.name}</Text>
|
|
{chat.labels.length > 0 ? (
|
|
<View style={styles.chatLabelTag}>
|
|
<Text style={styles.chatLabelTagText}>{chat.labels[0]}</Text>
|
|
</View>
|
|
) : null}
|
|
</View>
|
|
<Text style={styles.chatTime}>{chat.time}</Text>
|
|
</View>
|
|
<Text style={styles.chatTitle}>{chat.title}</Text>
|
|
<View style={styles.chatFooter}>
|
|
<Text style={styles.chatLastMessage} numberOfLines={1}>
|
|
{chat.lastMessage}
|
|
</Text>
|
|
{chat.unreadCount > 0 ? (
|
|
<View style={styles.unreadBadge}>
|
|
<Text style={styles.unreadBadgeText}>{chat.unreadCount}</Text>
|
|
</View>
|
|
) : null}
|
|
</View>
|
|
</View>
|
|
</Pressable>
|
|
);
|
|
}
|
|
|
|
/* ===================== 子组件:搜索栏 ===================== */
|
|
|
|
function SearchBar() {
|
|
return (
|
|
<View style={styles.searchBar}>
|
|
<Ionicons name="search-outline" size={18} color={colors.textHint} />
|
|
<Text style={styles.searchPlaceholder}>搜索候选人...</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 子组件:快捷操作栏 ===================== */
|
|
|
|
function QuickActions({ actions }: { actions: { icon: string; label: string }[] }) {
|
|
return (
|
|
<View style={styles.quickActionsCard}>
|
|
{actions.map((action) => (
|
|
<Pressable key={action.label} style={styles.quickAction}>
|
|
<Ionicons name={action.icon as keyof typeof Ionicons.glyphMap} size={20} color={colors.textSecondary} />
|
|
<Text style={styles.quickActionText}>{action.label}</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 子组件:空状态 ===================== */
|
|
|
|
function EmptyState({
|
|
dataReference,
|
|
onDiscover,
|
|
}: {
|
|
dataReference: { label: string; value: string }[];
|
|
onDiscover: () => void;
|
|
}) {
|
|
return (
|
|
<View style={styles.emptyCard}>
|
|
<View style={styles.emptyIconWrap}>
|
|
<Ionicons name="chatbubbles-outline" size={48} color={colors.primary} />
|
|
</View>
|
|
<Text style={styles.emptyTitle}>还没有沟通记录</Text>
|
|
<Text style={styles.emptySubtitle}>从人才库中发现合适的候选人,主动发起沟通吧</Text>
|
|
<Pressable style={styles.emptyBtn} onPress={onDiscover} hitSlop={6}>
|
|
<Text style={styles.emptyBtnText}>去发现人才</Text>
|
|
</Pressable>
|
|
|
|
<View style={styles.dataCard}>
|
|
<Text style={styles.dataTitle}>数据参考</Text>
|
|
{dataReference.map((item, index) => (
|
|
<View key={index} style={styles.dataRow}>
|
|
<Ionicons name="trending-up-outline" size={16} color={colors.success} />
|
|
<Text style={styles.dataText}>
|
|
{item.label}
|
|
<Text style={styles.dataValue}> {item.value}</Text>
|
|
</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 页面 ===================== */
|
|
|
|
const QUICK_ACTIONS = [
|
|
{ icon: 'mail-outline', label: '批量发邀请' },
|
|
{ icon: 'document-text-outline', label: '批量发Offer' },
|
|
{ icon: 'calendar-outline', label: '面试安排' },
|
|
{ icon: 'notifications-outline', label: '群发通知' },
|
|
];
|
|
|
|
export default function EnterpriseChatScreen() {
|
|
const router = useRouter();
|
|
const { communication, loading, error, refresh } = useEnterpriseCommunication();
|
|
const [activeTab, setActiveTab] = useState('all');
|
|
|
|
if (loading) {
|
|
return (
|
|
<SafeAreaView style={styles.container} edges={['top']}>
|
|
<Header companyName="智联科技" onBack={() => router.back()} />
|
|
<View style={styles.center}>
|
|
<ActivityIndicator size="large" color={colors.primary} />
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
if (error || !communication) {
|
|
return (
|
|
<SafeAreaView style={styles.container} edges={['top']}>
|
|
<Header companyName="智联科技" onBack={() => router.back()} />
|
|
<View style={styles.center}>
|
|
<Ionicons name="alert-circle-outline" size={40} color={colors.danger} />
|
|
<Text style={styles.tip}>加载失败</Text>
|
|
<Pressable style={styles.retryBtn} onPress={refresh}>
|
|
<Text style={styles.retryText}>重试</Text>
|
|
</Pressable>
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
|
<Header companyName={communication.companyName} onBack={() => router.back()} />
|
|
<TabBar tabs={communication.tabs} active={activeTab} onChange={setActiveTab} />
|
|
<ScrollView contentContainerStyle={styles.body} showsVerticalScrollIndicator={false}>
|
|
{communication.hasData ? (
|
|
<View>
|
|
<PendingTasks tasks={communication.pendingTasks} />
|
|
<SearchBar />
|
|
<View style={styles.chatListCard}>
|
|
{communication.chats.map((chat, index) => {
|
|
const isLast = index === communication.chats.length - 1;
|
|
return (
|
|
<View key={chat.id} style={isLast ? undefined : styles.chatBorder}>
|
|
<ChatRow chat={chat} />
|
|
</View>
|
|
);
|
|
})}
|
|
</View>
|
|
<QuickActions actions={QUICK_ACTIONS} />
|
|
</View>
|
|
) : (
|
|
<EmptyState
|
|
dataReference={communication.dataReference}
|
|
onDiscover={() => router.push('/talent-recommendation')}
|
|
/>
|
|
)}
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
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,
|
|
},
|
|
searchPlaceholder: {
|
|
marginLeft: 8,
|
|
fontSize: 14,
|
|
color: colors.textHint,
|
|
},
|
|
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',
|
|
},
|
|
});
|
|
|