Browse Source

fix(ui): 修复多处按钮绑定空函数导致点击无反馈

9 个页面存在 onPress={() => {}} 的死控件,用户点击后无任何反馈,无法区分「功能不存在」与「App 卡住了」。按项目既有约定分别处理:

- talent-recommendation:人才池快捷操作接入分发函数,「面试安排」改「面试评分」并跳 /interview-score(路由已存在),其余无对应页面者弹「功能开发中」
- hr-evaluation:「申诉」入口接 /appeal(路由已存在,此前是死按钮)
- chat/[id] / holland-result / interview / onboarding:右上角「更多 / 分享」无对应页面,统一弹提示
- progress:右上角漏斗按钮与页内 FilterTabs 功能重复且点击无响应,直接移除,保留同尺寸占位维持标题居中
- resume/export:「保存」仅在数据就绪后渲染,loading/error 态不再出现点了无反应的入口
- enterprise-chat:空状态文案「人才库」→「人才推荐」,与实际路由名对齐
master
doublex001 1 month ago
parent
commit
82771bc103
  1. 2
      app/(enterprise)/enterprise-chat.tsx
  2. 45
      app/(enterprise)/talent-recommendation.tsx
  3. 7
      app/chat/[id].tsx
  4. 12
      app/holland-result.tsx
  5. 4
      app/hr-evaluation.tsx
  6. 12
      app/interview.tsx
  7. 12
      app/onboarding.tsx
  8. 14
      app/progress.tsx
  9. 15
      app/resume/export.tsx

2
app/(enterprise)/enterprise-chat.tsx

@ -193,7 +193,7 @@ function EmptyState({
<Ionicons name="chatbubbles-outline" size={48} color={colors.primary} />
</View>
<Text style={styles.emptyTitle}>还没有沟通记录</Text>
<Text style={styles.emptySubtitle}>从人才库中发现合适的候选人,主动发起沟通吧</Text>
<Text style={styles.emptySubtitle}>从人才推荐中发现合适的候选人,主动发起沟通吧</Text>
<Pressable style={styles.emptyBtn} onPress={onDiscover} hitSlop={6}>
<Text style={styles.emptyBtnText}>去发现人才</Text>
</Pressable>

45
app/(enterprise)/talent-recommendation.tsx

@ -3,6 +3,7 @@ import { useRouter } from 'expo-router';
import { useState } from 'react';
import {
ActivityIndicator,
Alert,
Pressable,
ScrollView,
StyleSheet,
@ -140,7 +141,7 @@ function CandidateCard({ candidate }: { candidate: TalentCandidate }) {
/* ===================== 子组件:人才池数据 ===================== */
function TalentPool({ stats, actions }: { stats: { id: string; value: string; label: string }[]; actions: { icon: string; label: string }[] }) {
function TalentPool({ stats, actions, onPressAction }: { stats: { id: string; value: string; label: string }[]; actions: PoolActionItem[]; onPressAction: (action: PoolActionItem) => void }) {
return (
<View style={styles.poolCard}>
<Text style={styles.poolTitle}>人才池数据</Text>
@ -154,7 +155,7 @@ function TalentPool({ stats, actions }: { stats: { id: string; value: string; la
</View>
<View style={styles.poolActions}>
{actions.map((action) => (
<Pressable key={action.label} style={styles.poolAction}>
<Pressable key={action.label} style={styles.poolAction} onPress={() => onPressAction(action)}>
<Ionicons name={action.icon as keyof typeof Ionicons.glyphMap} size={20} color={colors.textSecondary} />
<Text style={styles.poolActionText}>{action.label}</Text>
</Pressable>
@ -217,10 +218,16 @@ function EmptyState({
/* ===================== 页面 ===================== */
const POOL_ACTIONS = [
/**
* 人才池快捷操作项:带 route 的跳转目标页,无对应页面的弹「功能开发中」提示。
* 与 enterprise-chat.tsx 的 QuickActionItem 保持同一约定。
*/
type PoolActionItem = { icon: string; label: string; route?: string };
const POOL_ACTIONS: PoolActionItem[] = [
{ icon: 'mail-outline', label: '批量发邀请' },
{ icon: 'document-text-outline', label: '批量发Offer' },
{ icon: 'calendar-outline', label: '面试安排' },
{ icon: 'calendar-outline', label: '面试评分', route: '/interview-score' },
{ icon: 'notifications-outline', label: '群发通知' },
];
@ -229,6 +236,26 @@ export default function TalentRecommendationScreen() {
const { recommendation, loading, error, refresh } = useTalentRecommendation();
const [activeTab, setActiveTab] = useState('all');
// 人才池快捷操作分发:有目标路由则跳转,无对应页面则提示「功能开发中」,
// 避免按钮点击后无任何反馈。与 enterprise-chat.tsx 的 handleQuickAction 同约定。
const handlePoolAction = (action: PoolActionItem) => {
if (action.route) {
router.push(action.route);
} else {
Alert.alert('提示', `${action.label}功能开发中,敬请期待`);
}
};
// 以下两处原先绑的是空函数(() => {}),按钮点击后无任何反馈。
// 暂无对应页面,按项目约定弹「功能开发中」提示。
const handleReport = () => {
Alert.alert('提示', '人才市场报告功能开发中,敬请期待');
};
const handleFilter = () => {
Alert.alert('提示', '筛选条件功能开发中,敬请期待');
};
if (loading) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
@ -264,18 +291,22 @@ export default function TalentRecommendationScreen() {
<View>
<TodayHeader
count={recommendation.todayCount}
onReport={() => {}}
onReport={handleReport}
/>
{recommendation.candidates.map((candidate) => (
<CandidateCard key={candidate.id} candidate={candidate} />
))}
<TalentPool stats={recommendation.poolStats} actions={POOL_ACTIONS} />
<TalentPool
stats={recommendation.poolStats}
actions={POOL_ACTIONS}
onPressAction={handlePoolAction}
/>
</View>
) : (
<EmptyState
actions={recommendation.recommendedActions}
dataReference={recommendation.dataReference}
onFilter={() => {}}
onFilter={handleFilter}
/>
)}
</ScrollView>

7
app/chat/[id].tsx

@ -325,6 +325,11 @@ export default function ChatDetailScreen() {
setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 100);
}, [input, sending, sendMessage]);
// 右上角「更多」暂无对应菜单页,按本文件既有约定弹提示(此前为空函数,点了无反应)
const handleMore = () => {
Alert.alert('提示', '功能开发中,敬请期待');
};
if (loading) {
return (
<SafeAreaView style={styles.safeArea} edges={['top']}>
@ -357,7 +362,7 @@ export default function ChatDetailScreen() {
avatar={detail.avatar}
online={detail.online}
onBack={() => router.back()}
onMore={() => {}}
onMore={handleMore}
/>
{detail.matchInfo && (

12
app/holland-result.tsx

@ -3,6 +3,7 @@ import { LinearGradient } from 'expo-linear-gradient';
import { useRouter } from 'expo-router';
import {
ActivityIndicator,
Alert,
Pressable,
ScrollView,
StyleSheet,
@ -228,10 +229,15 @@ export default function HollandResultScreen() {
const { assessment, loading, error, refresh } = useHollandAssessment();
const result = assessment?.result;
// 右上角「分享」暂无对应页面,按项目约定弹提示(此前绑定空函数,点击无任何反馈)
const handleShare = () => {
Alert.alert('提示', '分享测评报告功能开发中,敬请期待');
};
if (loading) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onShare={() => {}} />
<Header onBack={() => router.back()} onShare={handleShare} />
<Loading />
</SafeAreaView>
);
@ -240,7 +246,7 @@ export default function HollandResultScreen() {
if (error || !result) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onShare={() => {}} />
<Header onBack={() => router.back()} onShare={handleShare} />
<ErrorState onRetry={refresh} />
</SafeAreaView>
);
@ -248,7 +254,7 @@ export default function HollandResultScreen() {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onShare={() => {}} />
<Header onBack={() => router.back()} onShare={handleShare} />
<ScrollView contentContainerStyle={styles.body} showsVerticalScrollIndicator={false}>
<ResultHero code={result.code} name={result.name} dimensions={result.dimensions} />

4
app/hr-evaluation.tsx

@ -256,7 +256,7 @@ export default function HREvaluationScreen() {
if (loading) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onAppeal={() => {}} />
<Header onBack={() => router.back()} onAppeal={() => router.push('/appeal')} />
<Loading />
</SafeAreaView>
);
@ -265,7 +265,7 @@ export default function HREvaluationScreen() {
if (error || !evaluation) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onAppeal={() => {}} />
<Header onBack={() => router.back()} onAppeal={() => router.push('/appeal')} />
<ErrorState onRetry={refresh} />
</SafeAreaView>
);

12
app/interview.tsx

@ -2,6 +2,7 @@ import { Ionicons } from '@expo/vector-icons';
import { useRouter } from 'expo-router';
import {
ActivityIndicator,
Alert,
Pressable,
ScrollView,
StyleSheet,
@ -289,10 +290,15 @@ export default function InterviewScreen() {
await start();
};
// 右上角「分享」暂无对应页面,按项目约定弹提示(此前绑定空函数,点击无任何反馈)
const handleShare = () => {
Alert.alert('提示', '分享面试配置功能开发中,敬请期待');
};
if (loading) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onShare={() => {}} />
<Header onBack={() => router.back()} onShare={handleShare} />
<Loading />
</SafeAreaView>
);
@ -301,7 +307,7 @@ export default function InterviewScreen() {
if (error || !data) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onShare={() => {}} />
<Header onBack={() => router.back()} onShare={handleShare} />
<ErrorState onRetry={refresh} />
</SafeAreaView>
);
@ -309,7 +315,7 @@ export default function InterviewScreen() {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onShare={() => {}} />
<Header onBack={() => router.back()} onShare={handleShare} />
<ScrollView contentContainerStyle={styles.body} showsVerticalScrollIndicator={false}>
<ModeCards modes={data.modes} active={mode} onSelect={setMode} />

12
app/onboarding.tsx

@ -4,6 +4,7 @@ import { useRouter } from 'expo-router';
import { useEffect, useState } from 'react';
import {
ActivityIndicator,
Alert,
Pressable,
ScrollView,
StyleSheet,
@ -267,10 +268,15 @@ export default function OnboardingScreen() {
await toggle(itemId, done);
};
// 右上角「分享」暂无对应页面,按项目约定弹提示(此前绑定空函数,点击无任何反馈)
const handleShare = () => {
Alert.alert('提示', '分享入职清单功能开发中,敬请期待');
};
if (loading) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onShare={() => {}} />
<Header onBack={() => router.back()} onShare={handleShare} />
<Loading />
</SafeAreaView>
);
@ -279,7 +285,7 @@ export default function OnboardingScreen() {
if (error || !display) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onShare={() => {}} />
<Header onBack={() => router.back()} onShare={handleShare} />
<ErrorState onRetry={refresh} />
</SafeAreaView>
);
@ -287,7 +293,7 @@ export default function OnboardingScreen() {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onShare={() => {}} />
<Header onBack={() => router.back()} onShare={handleShare} />
<ScrollView contentContainerStyle={styles.body} showsVerticalScrollIndicator={false}>
<CountdownCard
daysLeft={display.daysLeft}

14
app/progress.tsx

@ -33,16 +33,16 @@ const STAGE_THEME: Record<JobProgressStage, { bg: string; text: string }> = {
/* ===================== 子组件:顶部导航 ===================== */
function Header({ onBack, onFilter }: { onBack: () => void; onFilter: () => void }) {
function Header({ onBack }: { onBack: () => void }) {
return (
<View style={styles.header}>
<Pressable style={styles.headerBtn} onPress={onBack} hitSlop={8}>
<Ionicons name="chevron-back" size={24} color={colors.textPrimary} />
</Pressable>
<Text style={styles.headerTitle}>求职进度</Text>
<Pressable style={styles.headerBtn} onPress={onFilter} hitSlop={8}>
<Ionicons name="funnel-outline" size={22} color={colors.textPrimary} />
</Pressable>
{/* 原右侧漏斗筛选按钮与页内 FilterTabs 功能重复且点击无反应,已移除。
此处保留同尺寸占位以维持标题居中。 */}
<View style={styles.headerBtn} />
</View>
);
}
@ -242,7 +242,7 @@ export default function ProgressScreen() {
if (loading) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onFilter={() => {}} />
<Header onBack={() => router.back()} />
<Loading />
</SafeAreaView>
);
@ -251,7 +251,7 @@ export default function ProgressScreen() {
if (error || !progress) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onFilter={() => {}} />
<Header onBack={() => router.back()} />
<ErrorState onRetry={refresh} />
</SafeAreaView>
);
@ -259,7 +259,7 @@ export default function ProgressScreen() {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onFilter={() => {}} />
<Header onBack={() => router.back()} />
<ScrollView contentContainerStyle={styles.body} showsVerticalScrollIndicator={false}>
<StatsCard stats={progress.stats} />
<OfferAlert count={progress.offers.length} />

15
app/resume/export.tsx

@ -36,16 +36,25 @@ const TEMPLATE_COLORS: Record<string, string> = {
/* ===================== 子组件:顶部导航 ===================== */
function Header({ onBack, onSave }: { onBack: () => void; onSave: () => void }) {
/**
* 顶部导航。onSave 可选:加载中 / 加载失败时设置尚未就绪(localSettings 为 null),
* 此时不渲染「保存」按钮,避免出现点了无反应的入口(比照 progress.tsx 移除无效按钮的处理)。
* 该态下仍渲染同尺寸占位,维持标题居中。
*/
function Header({ onBack, onSave }: { onBack: () => void; onSave?: () => void }) {
return (
<View style={styles.header}>
<Pressable style={styles.headerBtn} onPress={onBack} hitSlop={8}>
<Ionicons name="chevron-back" size={24} color={colors.textPrimary} />
</Pressable>
<Text style={styles.headerTitle}>导出简历</Text>
{onSave ? (
<Pressable style={styles.headerBtn} onPress={onSave} hitSlop={8}>
<Text style={styles.headerSave}>保存</Text>
</Pressable>
) : (
<View style={styles.headerBtn} />
)}
</View>
);
}
@ -264,7 +273,7 @@ export default function ResumeExportScreen() {
if (loading) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onSave={() => {}} />
<Header onBack={() => router.back()} />
<Loading />
</SafeAreaView>
);
@ -273,7 +282,7 @@ export default function ResumeExportScreen() {
if (error || !localSettings) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<Header onBack={() => router.back()} onSave={() => {}} />
<Header onBack={() => router.back()} />
<ErrorState onRetry={refresh} />
</SafeAreaView>
);

Loading…
Cancel
Save