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 { useAppealForm } from '../src/hooks';
import { colors } from '../src/theme';
import type { AppealReason, AppealType } from '../src/types';
/** 申诉类型选项 */
const APPEAL_TYPES: { id: AppealType; label: string; desc: string }[] = [
{ id: 'overall', label: '综合评分申诉', desc: '对整体评分结果有异议' },
{ id: 'single', label: '单条评价申诉', desc: '对某一条 HR 评价有异议' },
];
/** 申诉原因选项 */
const APPEAL_REASONS: { id: AppealReason; label: string }[] = [
{ id: 'untrue', label: '评分与事实不符' },
{ id: 'malicious', label: '评价存在主观偏见' },
{ id: 'privacy', label: '涉及个人隐私信息' },
{ id: 'other', label: '其他原因' },
];
/* ===================== 子组件:顶部导航 ===================== */
function Header({ onBack }: { onBack: () => void }) {
return (
评分申诉
);
}
/* ===================== 子组件:申诉须知 ===================== */
function NoticeCard() {
return (
申诉须知
1. 请在收到评价后 30 天内发起申诉,逾期将无法受理。
2. 请如实描述申诉原因,并提供相关凭证,虚假申诉将影响账号信用。
3. 平台将在 3 个工作日内完成审核,结果将通过站内消息通知你。
);
}
/* ===================== 子组件:申诉类型 ===================== */
function TypeSection({
active,
onSelect,
}: {
active: AppealType;
onSelect: (t: AppealType) => void;
}) {
return (
申诉类型 *
{APPEAL_TYPES.map((item) => {
const selected = item.id === active;
return (
onSelect(item.id)}
>
{item.label}
{item.desc}
);
})}
);
}
/* ===================== 子组件:申诉维度(多选) ===================== */
function DimensionSection({
dimensions,
active,
onToggle,
}: {
dimensions: { id: string; name: string }[];
active: string[];
onToggle: (id: string) => void;
}) {
return (
申请维度 (可多选)
{dimensions.map((dim) => {
const selected = active.includes(dim.id);
return (
onToggle(dim.id)}
>
{selected && (
)}
{dim.name}
);
})}
);
}
/* ===================== 子组件:申诉原因 ===================== */
function ReasonSection({
active,
onSelect,
}: {
active: AppealReason | null;
onSelect: (r: AppealReason) => void;
}) {
return (
申诉原因 *
{APPEAL_REASONS.map((item) => {
const selected = item.id === active;
return (
onSelect(item.id)}
>
{item.label}
);
})}
);
}
/* ===================== 子组件:详细说明 ===================== */
function DescriptionSection({
value,
onChange,
}: {
value: string;
onChange: (v: string) => void;
}) {
return (
详细说明 *
{value.length}/500
);
}
/* ===================== 子组件:上传凭证 ===================== */
function EvidenceSection() {
return (
上传凭证
点击上传图片或文件
支持 JPG / PNG / PDF,单个文件不超过 10MB
);
}
/* ===================== 子组件:联系方式 ===================== */
function ContactSection({
phone,
email,
onPhoneChange,
onEmailChange,
}: {
phone: string;
email: string;
onPhoneChange: (v: string) => void;
onEmailChange: (v: string) => void;
}) {
return (
联系方式 *
手机号
邮箱
);
}
/* ===================== 子组件:四态 ===================== */
function Loading() {
return (
正在加载申诉表单…
);
}
function ErrorState({ onRetry }: { onRetry: () => void }) {
return (
表单加载失败
点击重试
);
}
/* ===================== 页面 ===================== */
export default function AppealScreen() {
const router = useRouter();
const {
form,
loading,
error,
refresh,
type,
setType,
dimensions,
toggleDimension,
reason,
setReason,
reasonText,
setReasonText,
contactPhone,
setContactPhone,
contactEmail,
setContactEmail,
submit,
submitting,
} = useAppealForm();
const handleSubmit = async () => {
const result = await submit();
if (result) {
// TODO: 提交成功后跳转结果页或关闭弹层
}
};
if (loading) {
return (
);
}
if (error || !form) {
return (
);
}
return (
router.back()} />
{submitting ? (
) : (
提交申诉
)}
);
}
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' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
body: { paddingHorizontal: 16, paddingBottom: 100, paddingTop: 12 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: {
paddingHorizontal: 18,
paddingVertical: 8,
borderRadius: 16,
backgroundColor: colors.primaryLight,
},
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
noticeCard: {
backgroundColor: colors.infoLight,
borderRadius: 12,
padding: 14,
marginBottom: 16,
borderWidth: 1,
borderColor: colors.infoBorder,
},
noticeHead: { flexDirection: 'row', alignItems: 'center', marginBottom: 8 },
noticeTitle: { fontSize: 14, fontWeight: '700', color: colors.infoText, marginLeft: 6 },
noticeText: { fontSize: 12, color: colors.infoText, lineHeight: 20, marginBottom: 2 },
section: { marginBottom: 16 },
sectionTitle: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, marginBottom: 10 },
required: { color: colors.danger },
optional: { fontSize: 12, fontWeight: '400', color: colors.textHint },
typeCard: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.white,
borderRadius: 12,
padding: 14,
marginBottom: 10,
borderWidth: 1,
borderColor: colors.borderLight,
},
typeCardActive: { borderColor: colors.primary, backgroundColor: colors.primaryLight },
radioOuter: {
width: 18,
height: 18,
borderRadius: 9,
borderWidth: 1,
borderColor: colors.borderStrong,
},
typeBody: { flex: 1, marginLeft: 12 },
typeLabel: { fontSize: 14, fontWeight: '700', color: colors.textPrimary },
typeLabelActive: { color: colors.primaryText },
typeDesc: { fontSize: 12, color: colors.textSecondary, marginTop: 2 },
dimWrap: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 },
dimTag: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 18,
backgroundColor: colors.white,
borderWidth: 1,
borderColor: colors.border,
},
dimTagActive: { backgroundColor: colors.primary, borderColor: colors.primary },
dimTagText: { fontSize: 13, color: colors.textSecondary, marginLeft: 4 },
dimTagTextActive: { color: colors.white, fontWeight: '600' },
reasonRow: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.white,
borderRadius: 10,
paddingVertical: 12,
paddingHorizontal: 14,
marginBottom: 8,
borderWidth: 1,
borderColor: colors.borderLight,
},
reasonLabel: { fontSize: 14, color: colors.textPrimary, marginLeft: 10 },
reasonLabelActive: { color: colors.primaryText, fontWeight: '600' },
textArea: {
height: 120,
backgroundColor: colors.white,
borderRadius: 10,
borderWidth: 1,
borderColor: colors.borderLight,
padding: 12,
fontSize: 14,
color: colors.textPrimary,
},
counter: { marginTop: 6, fontSize: 12, color: colors.textHint, textAlign: 'right' },
uploadBox: {
backgroundColor: colors.white,
borderRadius: 10,
borderWidth: 1,
borderColor: colors.borderLight,
borderStyle: 'dashed',
paddingVertical: 24,
alignItems: 'center',
},
uploadText: { fontSize: 14, color: colors.textPrimary, marginTop: 8 },
uploadHint: { fontSize: 11, color: colors.textHint, marginTop: 4 },
card: {
backgroundColor: colors.white,
borderRadius: 10,
paddingHorizontal: 14,
borderWidth: 1,
borderColor: colors.borderLight,
},
inputRow: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
inputLabel: { width: 60, fontSize: 14, color: colors.textPrimary },
input: { flex: 1, fontSize: 14, color: colors.textPrimary },
footer: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
paddingHorizontal: 16,
paddingVertical: 12,
backgroundColor: colors.white,
borderTopWidth: 1,
borderTopColor: colors.borderLight,
},
submitBtn: {
backgroundColor: colors.primaryBtn,
borderRadius: 24,
paddingVertical: 14,
alignItems: 'center',
},
submitBtnDisabled: { backgroundColor: colors.textDisabled },
submitBtnText: { fontSize: 16, fontWeight: '700', color: colors.white },
});