feat: 初始化移动端项目基础结构

添加项目基础配置和核心功能模块:
- 配置 TypeScript 和 React Native 环境
- 实现认证状态管理
- 封装 API 请求客户端
- 搭建应用导航框架
This commit is contained in:
2026-02-24 10:34:59 +08:00
commit 42d332f77c
6 changed files with 616 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
/**
* AppNavigator - 应用导航配置
*
* 功能描述:
* 配置应用的路由导航结构,包括:
* - 认证流程(登录/注册)
* - 主应用流程底部Tab导航
* - 详情页面Stack导航
*
* 导航结构:
* RootStack
* ├── AuthStack (未登录)
* │ ├── Login
* │ └── Register
* └── MainStack (已登录)
* ├── TabNavigator
* │ ├── Home
* │ ├── Timeline
* │ ├── Gallery
* │ └── Profile
* └── StoryDetail
*
* @author Timeline Team
* @date 2024
*/
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { useAuthStore } from '@stores/authStore';
// 屏幕组件
import LoginScreen from '@screens/auth/LoginScreen';
import RegisterScreen from '@screens/auth/RegisterScreen';
import HomeScreen from '@screens/home/HomeScreen';
import TimelineScreen from '@screens/timeline/TimelineScreen';
import TimelineDetailScreen from '@screens/timeline/TimelineDetailScreen';
import GalleryScreen from '@screens/gallery/GalleryScreen';
import ProfileScreen from '@screens/profile/ProfileScreen';
import NotificationScreen from '@screens/notification/NotificationScreen';
import SettingsScreen from '@screens/settings/SettingsScreen';
// 图标组件
import Icon from '@components/common/Icon';
// 导航类型定义
export type RootStackParamList = {
Auth: undefined;
Main: undefined;
Login: undefined;
Register: undefined;
StoryDetail: { storyId: string };
Notification: undefined;
Settings: undefined;
};
export type TabParamList = {
Home: undefined;
Timeline: undefined;
Gallery: undefined;
Profile: undefined;
};
const Stack = createNativeStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator<TabParamList>();
/**
* Tab 导航器
* 主应用的底部标签导航
*/
const TabNavigator: React.FC = () => {
return (
<Tab.Navigator
screenOptions={{
headerShown: false,
tabBarActiveTintColor: '#1890ff',
tabBarInactiveTintColor: '#999',
tabBarStyle: {
backgroundColor: '#fff',
borderTopWidth: 1,
borderTopColor: '#f0f0f0',
paddingBottom: 8,
paddingTop: 8,
height: 60,
},
tabBarLabelStyle: {
fontSize: 12,
fontWeight: '500',
},
}}
>
<Tab.Screen
name="Home"
component={HomeScreen}
options={{
tabBarLabel: '首页',
tabBarIcon: ({ color, size }) => (
<Icon name="home" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Timeline"
component={TimelineScreen}
options={{
tabBarLabel: '时间线',
tabBarIcon: ({ color, size }) => (
<Icon name="timeline" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Gallery"
component={GalleryScreen}
options={{
tabBarLabel: '相册',
tabBarIcon: ({ color, size }) => (
<Icon name="image" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarLabel: '我的',
tabBarIcon: ({ color, size }) => (
<Icon name="user" size={size} color={color} />
),
}}
/>
</Tab.Navigator>
);
};
/**
* 认证导航器
* 未登录用户的认证流程
*/
const AuthStack: React.FC = () => {
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Register" component={RegisterScreen} />
</Stack.Navigator>
);
};
/**
* 主导航器
* 已登录用户的主应用流程
*/
const MainStack: React.FC = () => {
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="TabNavigator" component={TabNavigator} />
<Stack.Screen
name="StoryDetail"
component={TimelineDetailScreen}
options={{
headerShown: true,
title: '时间线详情',
}}
/>
<Stack.Screen name="Notification" component={NotificationScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
);
};
/**
* 根导航器
* 根据登录状态切换认证/主应用流程
*/
const AppNavigator: React.FC = () => {
const { isAuthenticated } = useAuthStore();
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{isAuthenticated ? (
<Stack.Screen name="Main" component={MainStack} />
) : (
<Stack.Screen name="Auth" component={AuthStack} />
)}
</Stack.Navigator>
</NavigationContainer>
);
};
export default AppNavigator;

138
src/services/apiClient.ts Normal file
View File

@@ -0,0 +1,138 @@
/**
* apiClient - API 请求客户端
*
* 功能描述:
* 封装 Axios 请求客户端,处理:
* - 请求拦截(添加 Token
* - 响应拦截(错误处理)
* - Token 刷新
* - 网络状态检测
*
* @author Timeline Team
* @date 2024
*/
import axios, { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios';
import { useAuthStore } from '@stores/authStore';
import NetInfo from '@react-native-community/netinfo';
// API 基础配置
const BASE_URL = __DEV__
? 'http://10.0.2.2:8080' // Android 模拟器
: 'https://api.timeline.com';
const TIMEOUT = 30000;
/**
* 创建 Axios 实例
*/
const apiClient: AxiosInstance = axios.create({
baseURL: BASE_URL,
timeout: TIMEOUT,
headers: {
'Content-Type': 'application/json',
},
});
/**
* 请求拦截器
* 添加 Token 到请求头
*/
apiClient.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
// 检查网络状态
const netInfo = await NetInfo.fetch();
if (!netInfo.isConnected) {
return Promise.reject(new Error('网络不可用,请检查网络连接'));
}
// 添加 Token
const token = useAuthStore.getState().token;
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error: AxiosError) => {
return Promise.reject(error);
}
);
/**
* 响应拦截器
* 处理错误和 Token 刷新
*/
apiClient.interceptors.response.use(
(response) => {
return response.data;
},
async (error: AxiosError) => {
const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };
// 处理 401 未授权错误
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
// 尝试刷新 Token
const refreshToken = useAuthStore.getState().refreshToken;
if (refreshToken) {
const response = await axios.post(`${BASE_URL}/auth/refresh`, {
refreshToken,
});
const { token } = response.data;
useAuthStore.getState().setToken(token);
// 重试原请求
if (originalRequest.headers) {
originalRequest.headers.Authorization = `Bearer ${token}`;
}
return apiClient(originalRequest);
}
} catch (refreshError) {
// Token 刷新失败,登出
useAuthStore.getState().logout();
return Promise.reject(refreshError);
}
}
// 处理其他错误
const errorMessage = getErrorMessage(error);
return Promise.reject(new Error(errorMessage));
}
);
/**
* 获取错误消息
*/
function getErrorMessage(error: AxiosError): string {
if (error.message === 'Network Error') {
return '网络错误,请检查网络连接';
}
if (error.response) {
const status = error.response.status;
const data: any = error.response.data;
switch (status) {
case 400:
return data?.message || '请求参数错误';
case 401:
return '登录已过期,请重新登录';
case 403:
return '没有权限访问';
case 404:
return '请求的资源不存在';
case 500:
return '服务器错误,请稍后重试';
default:
return data?.message || `请求失败 (${status})`;
}
}
return error.message || '未知错误';
}
export default apiClient;

123
src/stores/authStore.ts Normal file
View File

@@ -0,0 +1,123 @@
/**
* authStore - 认证状态管理 Store
*
* 功能描述:
* 使用 Zustand 管理用户认证状态。
*
* 状态:
* - isAuthenticated: 是否已认证
* - user: 用户信息
* - token: 访问令牌
*
* 操作:
* - login: 登录
* - logout: 登出
* - updateUser: 更新用户信息
*
* 持久化:
* - 使用 AsyncStorage 持久化 token
*
* @author Timeline Team
* @date 2024
*/
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { User } from '@types/user';
interface AuthState {
// 状态
isAuthenticated: boolean;
user: User | null;
token: string | null;
refreshToken: string | null;
isLoading: boolean;
// 操作
login: (token: string, refreshToken: string, user: User) => void;
logout: () => void;
updateUser: (user: Partial<User>) => void;
setToken: (token: string) => void;
setLoading: (loading: boolean) => void;
}
/**
* 认证状态 Store
*/
export const useAuthStore = create<AuthState>()(
persist(
(set, get) => ({
// 初始状态
isAuthenticated: false,
user: null,
token: null,
refreshToken: null,
isLoading: true,
/**
* 登录
* 保存 token 和用户信息
*/
login: (token, refreshToken, user) => {
set({
isAuthenticated: true,
token,
refreshToken,
user,
isLoading: false,
});
},
/**
* 登出
* 清除所有认证信息
*/
logout: () => {
set({
isAuthenticated: false,
user: null,
token: null,
refreshToken: null,
isLoading: false,
});
},
/**
* 更新用户信息
*/
updateUser: (userData) => {
const currentUser = get().user;
if (currentUser) {
set({
user: { ...currentUser, ...userData },
});
}
},
/**
* 设置 Token
*/
setToken: (token) => {
set({ token });
},
/**
* 设置加载状态
*/
setLoading: (loading) => {
set({ isLoading: loading });
},
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage),
partialize: (state) => ({
token: state.token,
refreshToken: state.refreshToken,
user: state.user,
isAuthenticated: state.isAuthenticated,
}),
}
)
);