import { Ionicons } from '@expo/vector-icons'; import { useFocusEffect, useLocalSearchParams, useRouter } from 'expo-router'; import { useCallback, useState } from 'react'; import { ActivityIndicator, Alert, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useLegalData, useLegalMembership, useLegalServiceRequests } from '../../src/hooks'; import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView'; import { colors , spacing } from '../../src/theme'; /* ===================== 顶部导航 ===================== */ function Header({ onBack }: { onBack: () => void }) { return ( 会员服务 ); } /* ===================== 页面 ===================== */ export default function LegalServiceScreen() { const router = useRouter(); const { id } = useLocalSearchParams(); const serviceId = Array.isArray(id) ? id[0] : id; const { services, loading, error, refresh } = useLegalData(); const { membership, refresh: refreshMembership } = useLegalMembership(); const { requests, loading: requestsLoading, error: requestsError, refresh: refreshRequests, submit, submitting, } = useLegalServiceRequests(); const [content, setContent] = useState(''); // 返回本页时刷新会员态与申请记录 useFocusEffect( useCallback(() => { refreshMembership(); refreshRequests(); }, [refreshMembership, refreshRequests]), ); const service = services.find((s) => s.id === serviceId); const handleSubmit = useCallback(async () => { if (!service) return; const text = content.trim(); if (!text) { Alert.alert('提示', '请先填写你的需求描述'); return; } try { await submit(service.id, text); setContent(''); Alert.alert('提交成功', '我们已收到你的申请,将尽快安排专人跟进。'); } catch (e) { Alert.alert('提交失败', (e as Error).message || '请稍后重试'); } }, [service, content, submit]); if (loading) { return (
router.back()} /> ); } // 接口失败:必须先于「服务不存在」判断,否则取数失败会伪装成业务空态, // 把用户引向「从法律服务页重新进入」这条无效的自救路径。 if (error) { return (
router.back()} /> ); } if (!service) { return (
router.back()} /> ); } // 未开通会员:拦截并引导开通(付费前不给权益) if (!membership) { return (
router.back()} /> {service.title} {service.description} 该服务为会员专享,开通后即可使用 router.push('/legal/upgrade')}> 开通会员 ); } const myRequests = requests.filter((r) => r.serviceId === service.id); const canSubmit = content.trim().length > 0 && !submitting; return (
router.back()} /> {/* 服务说明 */} {service.title} {service.description} {service.requestHint && {service.requestHint}} {/* 需求填写 */} 填写你的需求 {content.length}/500 [ styles.submitBtn, !canSubmit && styles.submitBtnDisabled, pressed && canSubmit && styles.submitBtnPressed, ]} onPress={handleSubmit} disabled={!canSubmit} > {submitting ? ( ) : ( 提交申请 )} {/* 我的申请记录 */} 我的申请记录 {requestsLoading ? ( ) : requestsError ? ( // 申请记录取数失败不能渲染成「暂无申请记录」——那等于告诉用户记录没了 申请记录加载失败 重试 ) : myRequests.length === 0 ? ( 暂无申请记录 ) : ( {myRequests.map((r) => ( {r.serviceName} {r.status === 'submitted' ? '已提交' : r.status === 'processing' ? '处理中' : '已完成'} {r.content} {new Date(r.createdAt).toLocaleString('zh-CN')} ))} )} ); } const styles = StyleSheet.create({ safeArea: { flex: 1, backgroundColor: colors.pageBg, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: spacing.card, height: 48, backgroundColor: colors.white, borderBottomWidth: 1, borderBottomColor: colors.borderLight, }, headerBtn: { width: 40, height: 40, alignItems: 'center', justifyContent: 'center', }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary, }, scroll: { flex: 1, }, content: { padding: spacing.card, paddingBottom: 24, }, card: { padding: spacing.card, borderRadius: spacing.card, backgroundColor: colors.white, borderWidth: 1, borderColor: colors.border, }, serviceHead: { flexDirection: 'row', alignItems: 'center', }, serviceIconWrap: { width: 44, height: 44, borderRadius: 22, backgroundColor: colors.actionLight, alignItems: 'center', justifyContent: 'center', }, serviceHeadBody: { flex: 1, marginLeft: 12, }, serviceName: { fontSize: 16, fontWeight: '700', color: colors.textPrimary, }, serviceDesc: { marginTop: 2, fontSize: 12, color: colors.textSecondary, lineHeight: 17, }, hint: { marginTop: 12, paddingTop: 12, borderTopWidth: 1, borderTopColor: colors.borderLight, fontSize: 13, lineHeight: 19, color: colors.textSecondary, }, sectionTitle: { marginTop: 20, marginBottom: 10, fontSize: 15, fontWeight: '700', color: colors.textPrimary, }, input: { minHeight: 120, padding: 12, borderRadius: spacing.card, backgroundColor: colors.white, borderWidth: 1, borderColor: colors.border, fontSize: 14, lineHeight: 20, color: colors.textPrimary, }, counter: { marginTop: 6, textAlign: 'right', fontSize: 12, color: colors.textHint, }, submitBtn: { marginTop: 16, height: 48, borderRadius: spacing.card, backgroundColor: colors.primaryBtn, alignItems: 'center', justifyContent: 'center', }, submitBtnPressed: { opacity: 0.9, }, submitBtnDisabled: { opacity: 0.5, }, submitBtnText: { fontSize: 16, fontWeight: '700', color: colors.white, }, requestsLoading: { marginTop: 16, }, emptyCard: { padding: spacing.card, borderRadius: spacing.card, backgroundColor: colors.white, alignItems: 'center', }, emptyText: { fontSize: 13, color: colors.textSecondary, }, errorCard: { padding: spacing.card, borderRadius: spacing.card, backgroundColor: colors.dangerLight, borderWidth: 1, borderColor: colors.dangerBorder, alignItems: 'center', gap: 8, }, errorText: { fontSize: 13, color: colors.dangerText, }, errorRetry: { fontSize: 13, fontWeight: '600', color: colors.primaryText, }, requestList: { gap: 8, }, requestCard: { padding: spacing.card, borderRadius: spacing.card, backgroundColor: colors.white, borderWidth: 1, borderColor: colors.border, }, requestHead: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, requestService: { flex: 1, fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginRight: 8, }, statusTag: { paddingHorizontal: spacing.card, paddingVertical: 2, borderRadius: 4, backgroundColor: colors.primaryLight, }, statusText: { fontSize: 11, fontWeight: '700', color: colors.primaryText, }, requestContent: { marginTop: 8, fontSize: 13, lineHeight: 19, color: colors.textSecondary, }, requestTime: { marginTop: 8, fontSize: 11, color: colors.textHint, }, lockedBox: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 24, }, lockedIconWrap: { width: 80, height: 80, borderRadius: 40, backgroundColor: colors.actionLight, alignItems: 'center', justifyContent: 'center', marginBottom: 16, }, lockedTitle: { fontSize: 18, fontWeight: '700', color: colors.textPrimary, }, lockedDesc: { marginTop: 8, fontSize: 14, lineHeight: 20, color: colors.textSecondary, textAlign: 'center', }, lockedTip: { marginTop: 12, fontSize: 13, fontWeight: '600', color: colors.actionText, }, lockedBtn: { marginTop: 28, width: 200, height: 46, borderRadius: 23, backgroundColor: colors.primaryBtn, alignItems: 'center', justifyContent: 'center', }, lockedBtnText: { fontSize: 15, fontWeight: '700', color: colors.white, }, });