PC端的部分适配

This commit is contained in:
2026-03-16 17:47:10 +08:00
parent 798dd7c9a0
commit cb2087f779
30 changed files with 4242 additions and 438 deletions

View File

@@ -1,9 +1,10 @@
/**
* 注册页 RegisterScreen响应式适配
* 注册页 RegisterScreen响应式适配 - 分栏布局
* 胡萝卜BBS - 用户注册
* 现代化设计 - 渐变背景 + 动画效果
* 注册表单在桌面端居中显示,限制最大宽度
* 支持横屏模式下的布局调整
*
* 布局规则:
* - 屏幕宽度 < 768px单栏布局橙色渐变背景
* - 屏幕宽度 >= 768px分栏布局左侧橙色右侧中性灰
*/
import React, { useState, useEffect, useRef } from 'react';
@@ -18,6 +19,7 @@ import {
ScrollView,
ActivityIndicator,
Animated,
StatusBar,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
@@ -29,17 +31,22 @@ import { authService, resolveAuthApiError } from '../../services/authService';
import { useAuthStore } from '../../stores';
import { RootStackParamList } from '../../navigation/types';
import { useResponsive, useResponsiveValue } from '../../hooks';
import { ResponsiveContainer } from '../../components/common';
import { showPrompt } from '../../services/promptService';
type RegisterNavigationProp = NativeStackNavigationProp<RootStackParamList, 'Auth'>;
// 分栏布局断点
const SPLIT_BREAKPOINT = 768;
export const RegisterScreen: React.FC = () => {
const navigation = useNavigation<RegisterNavigationProp>();
const register = useAuthStore((state) => state.register);
// 响应式布局
const { isWideScreen, isLandscape, isDesktop } = useResponsive();
const { isLandscape, width } = useResponsive();
// 是否使用分栏布局
const isSplitLayout = width >= SPLIT_BREAKPOINT;
const [username, setUsername] = useState('');
const [nickname, setNickname] = useState('');
@@ -61,11 +68,23 @@ export const RegisterScreen: React.FC = () => {
const scaleAnim = useRef(new Animated.Value(0.95)).current;
const inputFadeAnim = useRef(new Animated.Value(0)).current;
// 响应式值
const iconSize = useResponsiveValue({ xs: 48, sm: 52, md: 56, lg: 64, xl: 72 });
const iconContainerSize = useResponsiveValue({ xs: 80, sm: 88, md: 96, lg: 108, xl: 120 });
const titleSize = useResponsiveValue({ xs: 24, sm: 26, md: 28, lg: 30, xl: 32 });
const formMaxWidth = isDesktop ? 520 : isWideScreen ? 480 : undefined;
// 响应式值 - 大屏模式单独放大图标尺寸,更显大气
const iconSize = useResponsiveValue({
xs: 48, sm: 52, md: 56,
lg: isSplitLayout ? 120 : 64, // 大屏模式图标进一步放大
xl: isSplitLayout ? 140 : 72
});
const iconContainerSize = useResponsiveValue({
xs: 80, sm: 88, md: 96,
lg: isSplitLayout ? 180 : 108, // 大屏模式图标容器进一步放大
xl: isSplitLayout ? 200 : 120
});
const titleSize = useResponsiveValue({
xs: 24, sm: 26, md: 28,
lg: isSplitLayout ? 60 : 30, // 大屏模式标题文字大幅放大
xl: isSplitLayout ? 72 : 32
});
const formMaxWidth = isSplitLayout ? 480 : undefined;
// 启动入场动画
useEffect(() => {
@@ -219,64 +238,93 @@ export const RegisterScreen: React.FC = () => {
// 跳转到登录页
const handleGoToLogin = () => {
navigation.goBack();
navigation.navigate('Login' as any);
};
// 渲染左侧面板Logo和品牌信息- 优化大屏布局
const renderLeftPanel = () => (
<Animated.View
style={[
styles.leftPanel,
isSplitLayout && styles.splitLeftPanel, // 大屏模式专用布局
{
opacity: fadeAnim,
transform: [{ scale: scaleAnim }],
},
]}
>
{/* 用户图标 - 靠上显示 */}
<View style={[
styles.iconContainer,
styles.iconContainerTop,
isSplitLayout && styles.splitIconContainer, // 大屏模式图标容器样式
{ width: iconContainerSize, height: iconContainerSize, borderRadius: iconContainerSize / 2 }
]}>
<MaterialCommunityIcons
name="account-plus"
size={iconSize}
color="#FFF"
/>
</View>
{/* 品牌名称 - 优化大屏排版 */}
<Text style={[styles.title, isSplitLayout && styles.splitTitle]}></Text>
<Text style={[styles.subtitle, isSplitLayout && styles.splitSubtitle]}></Text>
{/* 装饰元素 - 大屏模式改为水平排列 + 大幅放大 */}
<View style={[styles.leftPanelDecorBottom, isSplitLayout && styles.splitLeftPanelDecorBottom]}>
{isSplitLayout ? (
// 大屏模式:水平排列 + 大幅放大图标
<View style={styles.splitDecorRow}>
<MaterialCommunityIcons name="account-plus-outline" size={80} color="rgba(255,255,255,0.4)" style={styles.splitDecorIcon} />
<MaterialCommunityIcons name="email-check-outline" size={90} color="rgba(255,255,255,0.45)" style={styles.splitDecorIcon} />
<MaterialCommunityIcons name="shield-check-outline" size={85} color="rgba(255,255,255,0.35)" style={styles.splitDecorIcon} />
</View>
) : (
// 小屏模式:保持原有横向排列
<View style={styles.decorRow}>
<MaterialCommunityIcons name="account-plus-outline" size={48} color="rgba(255,255,255,0.35)" />
<MaterialCommunityIcons name="email-check-outline" size={44} color="rgba(255,255,255,0.3)" />
<MaterialCommunityIcons name="shield-check-outline" size={40} color="rgba(255,255,255,0.25)" />
</View>
)}
</View>
</Animated.View>
);
// 渲染表单内容
const renderFormContent = () => (
<>
{/* 标题区域 */}
<Animated.View
style={[
styles.headerSection,
isLandscape && styles.headerSectionLandscape,
{
opacity: fadeAnim,
transform: [{ scale: scaleAnim }],
},
]}
>
<View style={[
styles.iconContainer,
{ width: iconContainerSize, height: iconContainerSize, borderRadius: iconContainerSize / 2 }
]}>
<MaterialCommunityIcons
name="account-plus"
size={iconSize}
color="#FFF"
/>
</View>
<Text style={[styles.title, { fontSize: titleSize }]}></Text>
<Text style={styles.subtitle}></Text>
</Animated.View>
{/* 表单卡片 */}
<Animated.View
<View style={isSplitLayout ? styles.splitFormWrapper : styles.singleFormWrapper}>
<Animated.View
style={[
styles.formCard,
formMaxWidth && { maxWidth: formMaxWidth, alignSelf: 'center', width: '100%' },
// 核心修改:大屏模式使用专用的阴影样式,小屏用原有阴影
isSplitLayout ? styles.splitFormCardShadow : styles.normalFormCardShadow,
formMaxWidth && { maxWidth: formMaxWidth, width: '100%' },
{
opacity: inputFadeAnim,
transform: [{ translateY: slideAnim }],
},
]}
>
<Text style={styles.formTitle}></Text>
{/* 用户名输入框 */}
<View style={styles.inputContainer}>
<View style={[
styles.inputWrapper,
isWideScreen && styles.inputWrapperWide,
isSplitLayout && styles.inputWrapperWide,
]}>
<MaterialCommunityIcons
name="account-outline"
size={22}
color={colors.primary.main}
<MaterialCommunityIcons
name="account-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[
styles.input,
isWideScreen && styles.inputWide,
isSplitLayout && styles.inputWide,
]}
placeholder="用户名3-20个字符"
placeholderTextColor={colors.text.hint}
@@ -305,18 +353,18 @@ export const RegisterScreen: React.FC = () => {
<View style={styles.inputContainer}>
<View style={[
styles.inputWrapper,
isWideScreen && styles.inputWrapperWide,
isSplitLayout && styles.inputWrapperWide,
]}>
<MaterialCommunityIcons
name="emoticon-outline"
size={22}
color={colors.primary.main}
<MaterialCommunityIcons
name="emoticon-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[
styles.input,
isWideScreen && styles.inputWide,
isSplitLayout && styles.inputWide,
]}
placeholder="昵称2-20个字符"
placeholderTextColor={colors.text.hint}
@@ -343,18 +391,18 @@ export const RegisterScreen: React.FC = () => {
<View style={styles.inputContainer}>
<View style={[
styles.inputWrapper,
isWideScreen && styles.inputWrapperWide,
isSplitLayout && styles.inputWrapperWide,
]}>
<MaterialCommunityIcons
name="email-outline"
size={22}
color={colors.primary.main}
<MaterialCommunityIcons
name="email-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[
styles.input,
isWideScreen && styles.inputWide,
isSplitLayout && styles.inputWide,
]}
placeholder="邮箱(必填)"
placeholderTextColor={colors.text.hint}
@@ -382,7 +430,7 @@ export const RegisterScreen: React.FC = () => {
{/* 邮箱验证码 */}
<View style={styles.inputContainer}>
<View style={styles.codeRow}>
<View style={[styles.inputWrapper, styles.codeInputWrapper, isWideScreen && styles.inputWrapperWide]}>
<View style={[styles.inputWrapper, styles.codeInputWrapper, isSplitLayout && styles.inputWrapperWide]}>
<MaterialCommunityIcons
name="shield-key-outline"
size={22}
@@ -390,7 +438,7 @@ export const RegisterScreen: React.FC = () => {
style={styles.inputIcon}
/>
<TextInput
style={[styles.input, isWideScreen && styles.inputWide]}
style={[styles.input, isSplitLayout && styles.inputWide]}
placeholder="邮箱验证码"
placeholderTextColor={colors.text.hint}
value={verificationCode}
@@ -419,18 +467,18 @@ export const RegisterScreen: React.FC = () => {
<View style={styles.inputContainer}>
<View style={[
styles.inputWrapper,
isWideScreen && styles.inputWrapperWide,
isSplitLayout && styles.inputWrapperWide,
]}>
<MaterialCommunityIcons
name="phone-outline"
size={22}
color={colors.primary.main}
<MaterialCommunityIcons
name="phone-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[
styles.input,
isWideScreen && styles.inputWide,
isSplitLayout && styles.inputWide,
]}
placeholder="手机号(选填)"
placeholderTextColor={colors.text.hint}
@@ -458,18 +506,18 @@ export const RegisterScreen: React.FC = () => {
<View style={styles.inputContainer}>
<View style={[
styles.inputWrapper,
isWideScreen && styles.inputWrapperWide,
isSplitLayout && styles.inputWrapperWide,
]}>
<MaterialCommunityIcons
name="lock-outline"
size={22}
color={colors.primary.main}
<MaterialCommunityIcons
name="lock-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[
styles.input,
isWideScreen && styles.inputWide,
isSplitLayout && styles.inputWide,
]}
placeholder="密码至少6位"
placeholderTextColor={colors.text.hint}
@@ -495,18 +543,18 @@ export const RegisterScreen: React.FC = () => {
<View style={styles.inputContainer}>
<View style={[
styles.inputWrapper,
isWideScreen && styles.inputWrapperWide,
isSplitLayout && styles.inputWrapperWide,
]}>
<MaterialCommunityIcons
name="lock-check-outline"
size={22}
color={colors.primary.main}
<MaterialCommunityIcons
name="lock-check-outline"
size={22}
color={colors.primary.main}
style={styles.inputIcon}
/>
<TextInput
style={[
styles.input,
isWideScreen && styles.inputWide,
isSplitLayout && styles.inputWide,
]}
placeholder="确认密码"
placeholderTextColor={colors.text.hint}
@@ -559,12 +607,12 @@ export const RegisterScreen: React.FC = () => {
activeOpacity={0.8}
>
<LinearGradient
colors={['#FF6B35', '#FF8F66']}
colors={[colors.primary.main, colors.primary.light]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={[
styles.registerButtonGradient,
isWideScreen && styles.registerButtonGradientWide,
isSplitLayout && styles.registerButtonGradientWide,
]}
>
{loading ? (
@@ -577,24 +625,60 @@ export const RegisterScreen: React.FC = () => {
</Animated.View>
{/* 底部登录提示 */}
<Animated.View
<Animated.View
style={[
styles.footerSection,
isSplitLayout && styles.splitFooterSection,
{ opacity: inputFadeAnim },
]}
>
<Text style={styles.footerText}></Text>
<Text style={[styles.footerText, isSplitLayout && styles.splitFooterText]}></Text>
<TouchableOpacity onPress={handleGoToLogin}>
<Text style={styles.loginLink}></Text>
<Text style={[styles.loginLink, isSplitLayout && styles.splitLoginLink]}></Text>
</TouchableOpacity>
</Animated.View>
</>
</View>
);
return (
<SafeAreaView style={styles.container}>
// 渲染分栏布局
const renderSplitLayout = () => (
<View style={styles.splitContainer}>
{/* 左侧橙色面板 */}
<LinearGradient
colors={['#FF6B35', '#FF8F66', '#FFB088']}
colors={[colors.primary.main, colors.primary.light, '#FFB088']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.leftPanelContainer}
>
{/* 装饰性背景元素 */}
<View style={styles.decorCircle1} />
<View style={styles.decorCircle2} />
<StatusBar barStyle="light-content" />
<SafeAreaView style={styles.leftPanelSafeArea}>
{renderLeftPanel()}
</SafeAreaView>
</LinearGradient>
{/* 右侧中性灰面板 */}
<View style={styles.rightPanelContainer}>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.rightPanelSafeArea}>
{/* 核心修复用View包裹确保垂直+水平居中 */}
<View style={styles.splitRightContentWrapper}>
{renderFormContent()}
</View>
</SafeAreaView>
</View>
</View>
);
// 渲染单栏布局
const renderSingleLayout = () => (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="light-content" />
<LinearGradient
colors={[colors.primary.main, colors.primary.light, '#FFB088']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.gradient}
@@ -603,43 +687,52 @@ export const RegisterScreen: React.FC = () => {
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.keyboardView}
>
{isWideScreen ? (
<ResponsiveContainer maxWidth={600}>
<ScrollView
contentContainerStyle={[
styles.scrollContent,
isLandscape && styles.scrollContentLandscape,
]}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
{/* 装饰性背景元素 */}
<View style={styles.decorCircle1} />
<View style={styles.decorCircle2} />
{renderFormContent()}
</ScrollView>
</ResponsiveContainer>
) : (
<ScrollView
contentContainerStyle={[
styles.scrollContent,
isLandscape && styles.scrollContentLandscape,
<ScrollView
contentContainerStyle={[
styles.scrollContent,
isLandscape && styles.scrollContentLandscape,
]}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
{/* 装饰性背景元素 */}
<View style={styles.decorCircle1} />
<View style={styles.decorCircle2} />
{/* 头部 */}
<Animated.View
style={[
styles.headerSection,
isLandscape && styles.headerSectionLandscape,
{
opacity: fadeAnim,
transform: [{ scale: scaleAnim }],
},
]}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
{/* 装饰性背景元素 */}
<View style={styles.decorCircle1} />
<View style={styles.decorCircle2} />
{renderFormContent()}
</ScrollView>
)}
<View style={[
styles.iconContainer,
{ width: iconContainerSize, height: iconContainerSize, borderRadius: iconContainerSize / 2 }
]}>
<MaterialCommunityIcons
name="account-plus"
size={iconSize}
color="#FFF"
/>
</View>
<Text style={[styles.title, { fontSize: titleSize }]}></Text>
<Text style={styles.subtitle}></Text>
</Animated.View>
{/* 表单 */}
{renderFormContent()}
</ScrollView>
</KeyboardAvoidingView>
</LinearGradient>
</SafeAreaView>
);
return isSplitLayout ? renderSplitLayout() : renderSingleLayout();
};
const styles = StyleSheet.create({
@@ -827,10 +920,16 @@ const styles = StyleSheet.create({
paddingTop: spacing.md,
paddingBottom: spacing.md,
},
splitFooterSection: {
paddingTop: spacing.xl,
},
footerText: {
fontSize: fontSizes.md,
color: 'rgba(255,255,255,0.9)',
},
splitFooterText: {
color: colors.text.primary,
},
loginLink: {
fontSize: fontSizes.md,
color: '#FFFFFF',
@@ -838,6 +937,141 @@ const styles = StyleSheet.create({
marginLeft: spacing.xs,
textDecorationLine: 'underline',
},
splitLoginLink: {
color: colors.primary.main,
},
// ========== 分栏布局样式 ==========
splitContainer: {
flex: 1,
flexDirection: 'row',
},
leftPanelContainer: {
flex: 1, // 50%
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
},
leftPanelSafeArea: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: spacing['4xl'], // 增加内边距,不贴边
},
leftPanel: {
alignItems: 'center',
paddingHorizontal: spacing['2xl'],
},
// 大屏模式左侧面板布局
splitLeftPanel: {
width: '100%',
justifyContent: 'center',
paddingVertical: 120,
},
// 大屏模式图标容器样式 - 增加阴影和内边距,更立体
splitIconContainer: {
borderWidth: 3,
borderColor: 'rgba(255,255,255,0.5)',
...shadows.lg,
},
iconContainerTop: {
marginTop: spacing['3xl'],
},
// 大屏模式标题样式 - 大幅放大,增加间距和文字效果
splitTitle: {
fontSize: 72, // 大幅放大主标题
marginBottom: 48,
textShadowOffset: { width: 0, height: 4 },
textShadowRadius: 8,
letterSpacing: 4, // 增加字间距,更显大气
fontWeight: '900',
},
// 大屏模式副标题样式 - 大幅放大
splitSubtitle: {
fontSize: 24, // 放大副标题
marginBottom: 120, // 增加底部间距
lineHeight: 36,
paddingHorizontal: spacing['4xl'],
letterSpacing: 1.5,
fontWeight: '500',
},
leftPanelDecorBottom: {
marginTop: spacing['2xl'],
},
// 大屏模式装饰元素布局
splitLeftPanelDecorBottom: {
marginTop: 120,
width: '100%',
alignItems: 'center',
},
// 大屏模式装饰元素水平排列
splitDecorRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
width: '100%',
gap: spacing['2xl'], // 增加图标间距
},
// 大屏模式装饰图标样式
splitDecorIcon: {
transform: [{ scale: 1.1 }],
opacity: 0.9,
},
decorRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
width: '80%',
},
rightPanelContainer: {
flex: 1, // 50%
backgroundColor: colors.background.paper,
},
rightPanelSafeArea: {
flex: 1,
// 移除左右内边距,交给子容器控制
},
// 分栏布局右侧内容包裹器(核心修复)
splitRightContentWrapper: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
// 新增:左右内边距,保证表单和边缘有间距
paddingHorizontal: spacing['2xl'],
},
// 表单内容包裹器(区分大屏/小屏)
splitFormWrapper: {
// 关键修改:设置最大宽度 + 自适应宽度,实现水平居中
maxWidth: 480,
width: '100%',
},
singleFormWrapper: {
flexGrow: 1,
width: '100%',
},
// 小屏模式:原有默认阴影
normalFormCardShadow: {
...shadows.md,
},
// 大屏模式:专用全向阴影
splitFormCardShadow: {
// iOS 全向柔和阴影
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.12,
shadowRadius: 12,
// Android 高程 + 轻微边框(模拟顶部阴影)
elevation: 8,
// 新增:极细微的顶部边框,强化区分度
borderTopWidth: 1,
borderTopColor: 'rgba(0,0,0,0.05)',
},
formTitle: {
fontSize: fontSizes['2xl'],
fontWeight: '700',
color: colors.text.primary,
marginBottom: spacing.xl,
textAlign: 'center',
},
});
export default RegisterScreen;