Files
frontend/src/screens/profile/HelpScreen.tsx

398 lines
12 KiB
TypeScript
Raw Normal View History

/**
* HelpScreen
* -
*
*/
import React, { useMemo, useState, useCallback } from 'react';
import {
View,
StyleSheet,
TouchableOpacity,
ScrollView,
Linking,
Alert,
} from 'react-native';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { StatusBar } from 'expo-status-bar';
import { useRouter } from 'expo-router';
import {
useAppColors,
spacing,
fontSizes,
borderRadius,
type AppColors,
} from '../../theme';
import { Text, SimpleHeader } from '../../components/common';
import { useResponsive, useResponsiveSpacing } from '../../hooks';
const CONTENT_MAX_WIDTH = 720;
// 威友橙主题色
const THEME_COLORS = {
primary: '#FF6B35',
primaryLight: '#FFF1EA',
};
// 官方联系邮箱
const CONTACT_EMAIL = 'system@qczlit.cn';
interface FAQItem {
q: string;
a: string;
/** 补充提示(如注意事项),与正文区分展示 */
note?: string;
}
interface FAQSection {
key: string;
title: string;
icon: string;
items: FAQItem[];
}
// 常见问题内容
const FAQ_SECTIONS: FAQSection[] = [
{
key: 'account',
title: '账号与认证',
icon: 'account-check-outline',
items: [
{
q: '如何通过身份认证?',
a: '进入「我的」页面,点击右上角齿轮状设置图标,选择「身份认证」。按提示填写学号/工号、真实姓名,并上传有效佐证材料。提交后通常等待 1-2 小时即可完成审核。',
},
{
q: '忘记密码怎么找回?',
a: '在登录页点击「忘记密码」,通过已绑定的手机号或校内邮箱进行重置。若均未绑定,请联系客服人工处理。',
},
{
q: '如何绑定手机号?',
a: '进入「我的」页面,点击「编辑资料」,选择「手机号」,输入手机号码并完成验证即可绑定。',
},
],
},
{
key: 'campus',
title: '校园工具',
icon: 'school-outline',
items: [
{
q: '如何查看课表?',
a: '进入「应用」页面,点击「课表」,点击左上角齿轮状设置图标,选择「同步教务系统」。输入学号/工号及教务系统密码即可同步。',
note: '该密码与统一身份认证密码不同。如遗忘,可登录新教务系统,进入「个人中心」→「修改密码」进行重置,默认密码为身份证号码后 6 位。',
},
{
q: '如何查找学习资料?',
a: '进入「应用」页面,点击「学习资料」,可按学科分类浏览,也可点击右上角搜索图标快速查找所需资料。',
},
],
},
{
key: 'notification',
title: '消息与通知',
icon: 'bell-outline',
items: [
{
q: '如何及时收到消息通知?',
a: '请先在手机系统「设置」→「通知管理」或「应用管理」中,找到本 App开启通知权限及自启动/后台运行权限;然后进入 App 内「我的」页面,点击右上角齿轮状设置图标,进入「通知管理」,确认所需消息提醒均已开启。设置完成后重启 App 即可生效。',
},
],
},
];
function createHelpStyles(colors: AppColors) {
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.paper,
},
scrollContent: {
paddingVertical: spacing.lg,
},
content: {
maxWidth: CONTENT_MAX_WIDTH,
alignSelf: 'center',
width: '100%',
},
// 分组
section: {
marginBottom: spacing.xl,
},
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: spacing['2xl'],
marginBottom: spacing.sm,
marginTop: spacing.sm,
},
sectionIcon: {
width: 22,
height: 22,
borderRadius: borderRadius.sm,
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.sm,
},
sectionTitle: {
fontWeight: '600',
fontSize: fontSizes.sm,
color: colors.text.secondary,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
// 问答卡片
faqList: {
backgroundColor: colors.background.paper,
borderTopWidth: StyleSheet.hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
borderColor: colors.divider,
overflow: 'hidden',
},
faqItem: {
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: colors.divider,
},
faqItemLast: {
borderBottomWidth: 0,
},
faqQuestion: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 16,
paddingHorizontal: spacing['2xl'],
},
questionText: {
flex: 1,
fontSize: fontSizes.lg,
fontWeight: '500',
color: colors.text.primary,
lineHeight: fontSizes.lg * 1.4,
},
faqAnswer: {
paddingHorizontal: spacing['2xl'],
paddingBottom: 16,
},
answerText: {
fontSize: fontSizes.md,
lineHeight: fontSizes.md * 1.7,
color: colors.text.secondary,
},
// 补充提示
noteContainer: {
flexDirection: 'row',
alignItems: 'flex-start',
marginTop: spacing.md,
padding: spacing.md,
backgroundColor: THEME_COLORS.primaryLight,
borderRadius: borderRadius.md,
gap: spacing.sm,
},
noteText: {
flex: 1,
fontSize: fontSizes.sm,
lineHeight: fontSizes.sm * 1.6,
color: colors.text.secondary,
},
// 联系我们
contactCard: {
marginHorizontal: spacing['2xl'],
marginTop: spacing.sm,
padding: spacing.xl,
borderRadius: borderRadius.xl,
backgroundColor: colors.background.default,
alignItems: 'center',
},
contactIconWrap: {
width: 52,
height: 52,
borderRadius: borderRadius.full,
backgroundColor: THEME_COLORS.primary,
justifyContent: 'center',
alignItems: 'center',
marginBottom: spacing.md,
},
contactTitle: {
fontSize: fontSizes.xl,
fontWeight: '600',
color: colors.text.primary,
marginBottom: spacing.xs,
},
contactDesc: {
fontSize: fontSizes.md,
color: colors.text.secondary,
textAlign: 'center',
marginBottom: spacing.lg,
lineHeight: fontSizes.md * 1.6,
},
contactButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: spacing.md,
paddingHorizontal: spacing.xl,
borderRadius: borderRadius.full,
backgroundColor: THEME_COLORS.primary,
gap: spacing.sm,
},
contactButtonText: {
fontSize: fontSizes.md,
fontWeight: '600',
color: '#fff',
},
// 底部版权
footer: {
alignItems: 'center',
marginTop: spacing.xl,
paddingBottom: spacing.xl,
},
footerText: {
fontSize: fontSizes.sm,
color: colors.text.hint,
textAlign: 'center',
},
});
}
export const HelpScreen: React.FC = () => {
const colors = useAppColors();
const styles = useMemo(() => createHelpStyles(colors), [colors]);
const insets = useSafeAreaInsets();
const router = useRouter();
const { isMobile } = useResponsive();
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
// 当前展开的问题 key同一时刻仅展开一个
const [expandedKey, setExpandedKey] = useState<string | null>(
`${FAQ_SECTIONS[0].key}-0`,
);
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
const toggleItem = useCallback((key: string) => {
setExpandedKey((prev) => (prev === key ? null : key));
}, []);
const handleContactEmail = useCallback(async () => {
const url = `mailto:${CONTACT_EMAIL}`;
try {
const canOpen = await Linking.canOpenURL(url);
if (canOpen) {
await Linking.openURL(url);
} else {
Alert.alert('联系我们', `请发送邮件至:${CONTACT_EMAIL}`);
}
} catch {
Alert.alert('联系我们', `请发送邮件至:${CONTACT_EMAIL}`);
}
}, []);
const renderFAQItem = (
section: FAQSection,
item: FAQItem,
index: number,
) => {
const key = `${section.key}-${index}`;
const isExpanded = expandedKey === key;
const isLast = index === section.items.length - 1;
return (
<View
key={key}
style={[styles.faqItem, isLast && styles.faqItemLast]}
>
<TouchableOpacity
style={styles.faqQuestion}
onPress={() => toggleItem(key)}
activeOpacity={0.7}
>
<Text style={styles.questionText}>{item.q}</Text>
<MaterialCommunityIcons
name={isExpanded ? 'chevron-up' : 'chevron-down'}
size={20}
color={colors.text.hint}
/>
</TouchableOpacity>
{isExpanded && (
<View style={styles.faqAnswer}>
<Text style={styles.answerText}>{item.a}</Text>
{item.note && (
<View style={styles.noteContainer}>
<MaterialCommunityIcons
name="alert-circle-outline"
size={16}
color={THEME_COLORS.primary}
/>
<Text style={styles.noteText}>{item.note}</Text>
</View>
)}
</View>
)}
</View>
);
};
const renderSection = (section: FAQSection) => (
<View key={section.key} style={styles.section}>
<View style={styles.sectionHeader}>
<View style={[styles.sectionIcon, { backgroundColor: THEME_COLORS.primaryLight }]}>
<MaterialCommunityIcons name={section.icon as any} size={14} color={THEME_COLORS.primary} />
</View>
<Text style={styles.sectionTitle}>{section.title}</Text>
</View>
<View style={styles.faqList}>
{section.items.map((item, index) => renderFAQItem(section, item, index))}
</View>
</View>
);
return (
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
<StatusBar style="auto" />
<SimpleHeader title="帮助与反馈" onBack={() => router.back()} />
<ScrollView
contentContainerStyle={[
styles.scrollContent,
{ paddingBottom: scrollBottomInset, paddingHorizontal: responsivePadding },
]}
showsVerticalScrollIndicator={false}
>
<View style={styles.content}>
{FAQ_SECTIONS.map(renderSection)}
{/* 联系我们 */}
<View style={styles.contactCard}>
<View style={styles.contactIconWrap}>
<MaterialCommunityIcons name="email-outline" size={26} color="#fff" />
</View>
<Text style={styles.contactTitle}></Text>
<Text style={styles.contactDesc}>
</Text>
<TouchableOpacity
style={styles.contactButton}
onPress={handleContactEmail}
activeOpacity={0.8}
>
<MaterialCommunityIcons name="email-fast-outline" size={18} color="#fff" />
<Text style={styles.contactButtonText}>{CONTACT_EMAIL}</Text>
</TouchableOpacity>
</View>
<View style={styles.footer}>
<Text style={styles.footerText}>© 2026 </Text>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
};
export default HelpScreen;