# 03-用户认证页面 ## 目标 实现 APP 端登录和注册页面。 --- ## UI 设计参考 > 参考设计稿:`files/ui/登录页.png` ### 页面布局 | 区域 | 设计要点 | |------|----------| | 顶部 | 绿色渐变背景 (`#10B981 → #2EC4B6`) + 医疗插图 | | Logo | "AI健康助手" 标题(白色 32px)+ slogan | | 表单卡片 | 白色背景,圆角 16px | | 输入框 | 圆角 12px,左侧带图标 | | 主按钮 | 绿色 `#10B981`,圆角 24px,高度 48px | ### 样式常量 ```typescript const colors = { primary: '#10B981', primaryDark: '#059669', background: '#10B981', cardBackground: '#FFFFFF', inputBackground: '#F3F4F6', textPrimary: '#1F2937', textSecondary: '#6B7280', textHint: '#9CA3AF', } const spacing = { screenPadding: 20, cardPadding: 24, inputMargin: 16, } const borderRadius = { card: 16, input: 12, button: 24, } ``` --- ## 前置要求 - 导航配置完成 - 后端认证接口可用 --- ## 实施步骤 ### 步骤 1:创建认证 API 创建 `src/api/auth.ts`: ```typescript import request from './request' export interface LoginRequest { phone: string password: string } export interface RegisterRequest { phone: string password: string nickname?: string } export interface AuthResponse { token: string user_id: number nickname: string survey_completed: boolean } export const login = (data: LoginRequest): Promise => { return request.post('/auth/login', data) } export const register = (data: RegisterRequest): Promise => { return request.post('/auth/register', data) } ``` ### 步骤 2:创建登录页面 更新 `src/screens/auth/LoginScreen.tsx`: ```typescript import React, { useState } from 'react' import { View, Text, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, } from 'react-native' import { TextInput, Button } from 'react-native-paper' import { useNavigation } from '@react-navigation/native' import { useUserStore } from '../../stores/userStore' import { login } from '../../api/auth' import type { AuthNavigationProp } from '../../navigation/types' const LoginScreen = () => { const navigation = useNavigation() const { setToken, setUser } = useUserStore() const [phone, setPhone] = useState('') const [password, setPassword] = useState('') const [loading, setLoading] = useState(false) const [showPassword, setShowPassword] = useState(false) const handleLogin = async () => { if (!phone.trim() || !password.trim()) { return } setLoading(true) try { const res = await login({ phone, password }) setToken(res.token) setUser({ id: res.user_id, nickname: res.nickname, phone, email: '', avatar: '', survey_completed: res.survey_completed, }) } catch (error) { // 错误已在拦截器处理 } finally { setLoading(false) } } return ( 健康AI助手 您的智能健康管家 } /> } right={ setShowPassword(!showPassword)} /> } /> 还没有账号? ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#667eea', }, scrollContent: { flexGrow: 1, justifyContent: 'center', padding: 20, }, header: { alignItems: 'center', marginBottom: 40, }, title: { fontSize: 32, fontWeight: 'bold', color: '#fff', marginBottom: 8, }, subtitle: { fontSize: 16, color: 'rgba(255,255,255,0.8)', }, form: { backgroundColor: '#fff', borderRadius: 16, padding: 24, }, input: { marginBottom: 16, }, button: { marginTop: 8, borderRadius: 8, }, buttonContent: { paddingVertical: 8, }, footer: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginTop: 16, }, footerText: { color: '#666', }, }) export default LoginScreen ``` ### 步骤 3:创建注册页面 创建 `src/screens/auth/RegisterScreen.tsx`: ```typescript import React, { useState } from 'react' import { View, Text, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, Alert, } from 'react-native' import { TextInput, Button, Checkbox } from 'react-native-paper' import { useNavigation } from '@react-navigation/native' import { useUserStore } from '../../stores/userStore' import { register } from '../../api/auth' import type { AuthNavigationProp } from '../../navigation/types' const RegisterScreen = () => { const navigation = useNavigation() const { setToken, setUser } = useUserStore() const [phone, setPhone] = useState('') const [nickname, setNickname] = useState('') const [password, setPassword] = useState('') const [confirmPassword, setConfirmPassword] = useState('') const [agreement, setAgreement] = useState(false) const [loading, setLoading] = useState(false) const [showPassword, setShowPassword] = useState(false) const handleRegister = async () => { if (!phone.trim() || !password.trim()) { Alert.alert('提示', '请输入手机号和密码') return } if (password !== confirmPassword) { Alert.alert('提示', '两次输入的密码不一致') return } if (!agreement) { Alert.alert('提示', '请先同意用户协议和隐私政策') return } setLoading(true) try { const res = await register({ phone, password, nickname: nickname || undefined, }) setToken(res.token) setUser({ id: res.user_id, nickname: res.nickname, phone, email: '', avatar: '', survey_completed: false, }) } catch (error) { // 错误已在拦截器处理 } finally { setLoading(false) } } return ( 创建账号 } /> } /> } right={ setShowPassword(!showPassword)} /> } /> } /> setAgreement(!agreement)} /> 我已阅读并同意《用户协议》和《隐私政策》 已有账号? ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#667eea', }, scrollContent: { flexGrow: 1, justifyContent: 'center', padding: 20, }, header: { alignItems: 'center', marginBottom: 24, }, title: { fontSize: 28, fontWeight: 'bold', color: '#fff', }, form: { backgroundColor: '#fff', borderRadius: 16, padding: 24, }, input: { marginBottom: 16, }, checkboxRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 16, }, agreementText: { flex: 1, fontSize: 13, color: '#666', }, button: { borderRadius: 8, }, buttonContent: { paddingVertical: 8, }, footer: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginTop: 16, }, footerText: { color: '#666', }, }) export default RegisterScreen ``` --- ## 需要创建的文件清单 | 文件路径 | 说明 | |----------|------| | `src/api/auth.ts` | 认证 API | | `src/screens/auth/LoginScreen.tsx` | 登录页面 | | `src/screens/auth/RegisterScreen.tsx` | 注册页面 | --- ## 验收标准 - [ ] 登录页面 UI 正常显示 - [ ] 注册页面 UI 正常显示 - [ ] 登录功能正常 - [ ] 注册功能正常 - [ ] 状态切换正确触发导航 --- ## 预计耗时 25-30 分钟 --- ## 下一步 完成后进入 `04-APP开发/04-健康调查页面.md` --- ## 备注 由于 APP 端实现与 Web 端逻辑类似,后续文档将提供精简版说明和关键代码,完整实现可参考 Web 端对应模块。 APP 端剩余页面(健康调查、体质测评、AI对话、个人中心)的详细实现将根据实际开发进度逐步补充。关键差异点: 1. **UI 框架**:使用 React Native Paper 代替 Element Plus 2. **导航**:使用 React Navigation 代替 Vue Router 3. **状态管理**:使用 Zustand 代替 Pinia 4. **样式**:使用 StyleSheet 代替 CSS 5. **表单**:使用 React Hook Form 或原生 useState