import { Ionicons } from '@expo/vector-icons'; import { useFocusEffect, useRouter } from 'expo-router'; import { useCallback, useState } from 'react'; import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useLegalData, useLegalMembership } from '../src/hooks'; import { colors , spacing } from '../src/theme'; import type { LegalFAQ, LegalPlan, LegalService, LegalTemplate } from '../src/types'; /* ===================== 子组件:顶部导航 ===================== */ function Header({ onBack }: { onBack: () => void }) { return ( 阿布阿诺法律服务 ); } /* ===================== 子组件:品牌卡片 ===================== */ function BrandCard({ name, company, slogan, logo }: { name: string; company: string; slogan: string; logo: string }) { return ( {logo} {name} {company} {slogan} ); } /* ===================== 子组件:服务入口 ===================== */ function ServiceCard({ service, isUnlocked, onPress, }: { service: LegalService; /** 会员服务是否已解锁(已开通会员) */ isUnlocked: boolean; onPress: () => void; }) { const isMember = service.type === 'member'; return ( {service.title} {isMember && ( {isUnlocked ? '已开通' : '会员'} )} {service.description} ); } /* ===================== 子组件:FAQ 卡片 ===================== */ function FAQItem({ faq }: { faq: LegalFAQ }) { const [expanded, setExpanded] = useState(false); return ( setExpanded((v) => !v)}> {faq.question} {expanded && {faq.answer}} ); } /* ===================== 子组件:模板卡片 ===================== */ function TemplateCard({ template }: { template: LegalTemplate }) { return ( {template.name} {template.description} ); } /* ===================== 子组件:会员套餐 ===================== */ function PlanCard({ plan, onPress }: { plan: LegalPlan; onPress?: () => void }) { return ( [ styles.planCard, plan.recommended && styles.planCardRecommended, pressed && styles.planPressed, ]} onPress={onPress} disabled={!onPress} > {plan.recommended && ( 推荐 )} {plan.name} ¥ {plan.price} {plan.period} {plan.benefits.map((benefit, idx) => ( {benefit} ))} 立即开通 ); } /* ===================== 页面 ===================== */ export default function LegalScreen() { const router = useRouter(); const { brand, services, faqs, templates, plans, loading, error, refresh } = useLegalData(); const { membership, refresh: refreshMembership } = useLegalMembership(); // 返回本页时刷新会员态(支付成功后需即时反映) useFocusEffect( useCallback(() => { refreshMembership(); }, [refreshMembership]), ); const freeServices = services.filter((s) => s.type === 'free'); const memberServices = services.filter((s) => s.type === 'member'); const handleServicePress = (service: LegalService) => { if (service.action === 'chat') { router.push('/legal/chat'); } else if (service.action === 'templates') { router.push('/legal/templates'); } else if (service.action === 'faq') { router.push('/legal/faq'); } else if (service.action === 'upgrade') { // 已开通会员 → 进入服务使用页(权益兑现);未开通 → 引导开通 if (membership) { router.push(`/legal/service?id=${service.id}`); } else { router.push('/legal/upgrade'); } } }; return (
router.back()} /> {loading ? ( ) : error ? ( 加载失败,请重试 重试 ) : ( {brand && ( )} {/* 下面两个网格此前没有空态保护,同文件的模板/FAQ/套餐三处都做了 —— 统一补齐 */} {freeServices.length === 0 ? ( ) : ( {freeServices.map((service) => ( handleServicePress(service)} /> ))} )} {memberServices.length === 0 ? ( ) : ( {memberServices.map((service) => ( handleServicePress(service)} /> ))} )} {templates.length > 0 && ( <> {templates.map((template) => ( ))} )} {faqs.length > 0 && ( <> {faqs.map((faq) => ( ))} )} {plans.length > 0 && ( <> {membership && ( 您已开通{membership.planName},可畅享会员专属服务 )} {plans.map((plan) => ( router.push(`/legal/checkout?planId=${plan.id}`)} /> ))} )} router.push('/legal/chat')}> 开始法律咨询 )} ); } /* ===================== 公共子组件 ===================== */ /** 服务网格的空态提示(区块级,不占满整屏) */ function EmptyGridHint({ text }: { text: string }) { return ( {text} ); } function SectionTitle({ title }: { title: string }) { return ( {title} ); } 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, }, brandCard: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.primaryLight, borderRadius: spacing.card, padding: spacing.card, marginBottom: 8, borderWidth: 1, borderColor: colors.primaryBorder, }, brandLogo: { width: 56, height: 56, borderRadius: 28, backgroundColor: colors.white, alignItems: 'center', justifyContent: 'center', }, brandLogoText: { fontSize: 28, }, brandBody: { flex: 1, marginLeft: 12, }, brandName: { fontSize: 18, fontWeight: '700', color: colors.primaryText, }, brandCompany: { marginTop: 2, fontSize: 12, color: colors.textSecondary, }, brandSlogan: { marginTop: 4, fontSize: 13, fontWeight: '600', color: colors.primary, }, sectionHeader: { flexDirection: 'row', alignItems: 'center', marginTop: 20, marginBottom: 10, }, sectionBar: { width: 4, height: 16, borderRadius: 2, backgroundColor: colors.primary, marginRight: 8, }, sectionTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary, }, serviceGrid: { gap: 8, }, emptyHint: { backgroundColor: colors.surface, borderRadius: spacing.card, padding: spacing.card, alignItems: 'center', gap: 8, }, emptyHintText: { fontSize: 13, color: colors.textSecondary, }, serviceCard: { flexDirection: 'row', alignItems: 'center', padding: spacing.card, borderRadius: spacing.card, backgroundColor: colors.white, }, serviceCardFree: { borderLeftWidth: 3, borderLeftColor: colors.primary, }, serviceCardMember: { borderLeftWidth: 3, borderLeftColor: colors.action, }, serviceIconWrap: { width: 40, height: 40, borderRadius: 20, backgroundColor: colors.pageBg, alignItems: 'center', justifyContent: 'center', }, serviceBody: { flex: 1, marginLeft: 12, marginRight: 8, }, serviceTitleRow: { flexDirection: 'row', alignItems: 'center', gap: 6, }, serviceTitle: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, }, memberTag: { paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, backgroundColor: colors.actionLight, }, memberTagText: { fontSize: 10, fontWeight: '700', color: colors.actionText, }, serviceDesc: { marginTop: 2, fontSize: 12, color: colors.textSecondary, lineHeight: 17, }, templateGrid: { gap: 8, }, templateCard: { flexDirection: 'row', alignItems: 'center', padding: spacing.card, borderRadius: spacing.card, backgroundColor: colors.white, }, templateBody: { flex: 1, marginLeft: 12, }, templateName: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, }, templateDesc: { marginTop: 2, fontSize: 12, color: colors.textSecondary, }, faqCard: { backgroundColor: colors.white, borderRadius: spacing.card, paddingHorizontal: spacing.card, }, faqItem: { paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: colors.borderLight, }, faqRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, faqQ: { flex: 1, fontSize: 14, fontWeight: '600', color: colors.textPrimary, marginRight: 8, }, faqA: { marginTop: 8, fontSize: 13, lineHeight: 19, color: colors.textSecondary, }, planGrid: { flexDirection: 'row', gap: 8, }, planCard: { flex: 1, padding: spacing.card, borderRadius: spacing.card, backgroundColor: colors.white, borderWidth: 1, borderColor: colors.border, }, planPressed: { opacity: 0.85, }, planCtaRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', marginTop: 12, gap: 2, }, planCtaText: { fontSize: 13, fontWeight: '700', color: colors.action, }, memberBanner: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: spacing.card, paddingVertical: spacing.card, borderRadius: spacing.card, backgroundColor: colors.actionLight, borderWidth: 1, borderColor: colors.action, marginBottom: 8, }, memberBannerText: { flex: 1, fontSize: 13, fontWeight: '600', color: colors.actionText, }, planCardRecommended: { borderColor: colors.action, backgroundColor: colors.actionLight, }, recommendedTag: { alignSelf: 'flex-start', paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, backgroundColor: colors.action, marginBottom: 8, }, recommendedText: { fontSize: 10, fontWeight: '700', color: colors.white, }, planName: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, }, planPriceRow: { flexDirection: 'row', alignItems: 'baseline', marginTop: 8, marginBottom: 10, }, planCurrency: { fontSize: 14, fontWeight: '700', color: colors.actionText, }, planPrice: { fontSize: 28, fontWeight: '700', color: colors.actionText, }, planPeriod: { fontSize: 13, color: colors.textSecondary, }, benefits: { gap: 6, }, benefitRow: { flexDirection: 'row', alignItems: 'center', gap: 6, }, benefitText: { fontSize: 12, color: colors.textSecondary, }, consultBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, marginTop: 20, height: 48, borderRadius: spacing.card, backgroundColor: colors.primaryBtn, }, consultText: { fontSize: 15, fontWeight: '700', color: colors.white, }, stateBox: { flex: 1, alignItems: 'center', justifyContent: 'center', }, stateText: { marginTop: 12, fontSize: 14, color: colors.textSecondary, }, retryBtn: { marginTop: 16, paddingHorizontal: 24, paddingVertical: spacing.card, borderRadius: 18, backgroundColor: colors.primaryBtn, }, retryText: { fontSize: 13, fontWeight: '600', color: colors.white, }, });