From 42d332f77c6555cae978337f28b0a60d29fb9229 Mon Sep 17 00:00:00 2001
From: jianghao <332515344@qq.com>
Date: Tue, 24 Feb 2026 10:34:59 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E7=A7=BB?=
=?UTF-8?q?=E5=8A=A8=E7=AB=AF=E9=A1=B9=E7=9B=AE=E5=9F=BA=E7=A1=80=E7=BB=93?=
=?UTF-8?q?=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
添加项目基础配置和核心功能模块:
- 配置 TypeScript 和 React Native 环境
- 实现认证状态管理
- 封装 API 请求客户端
- 搭建应用导航框架
---
App.tsx | 56 +++++++++
package.json | 67 +++++++++++
src/navigation/AppNavigator.tsx | 200 ++++++++++++++++++++++++++++++++
src/services/apiClient.ts | 138 ++++++++++++++++++++++
src/stores/authStore.ts | 123 ++++++++++++++++++++
tsconfig.json | 32 +++++
6 files changed, 616 insertions(+)
create mode 100644 App.tsx
create mode 100644 package.json
create mode 100644 src/navigation/AppNavigator.tsx
create mode 100644 src/services/apiClient.ts
create mode 100644 src/stores/authStore.ts
create mode 100644 tsconfig.json
diff --git a/App.tsx b/App.tsx
new file mode 100644
index 0000000..3d4292f
--- /dev/null
+++ b/App.tsx
@@ -0,0 +1,56 @@
+/**
+ * Timeline 移动端 App 入口
+ *
+ * 功能描述:
+ * React Native 应用入口,配置导航和全局状态管理。
+ *
+ * @author Timeline Team
+ * @date 2024
+ */
+
+import React from 'react';
+import { StatusBar } from 'react-native';
+import { SafeAreaProvider } from 'react-native-safe-area-context';
+import { GestureHandlerRootView } from 'react-native-gesture-handler';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import AppNavigator from './src/navigation/AppNavigator';
+
+// 创建 React Query 客户端
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 5 * 60 * 1000, // 5分钟
+ cacheTime: 10 * 60 * 1000, // 10分钟
+ retry: 2,
+ refetchOnWindowFocus: false,
+ },
+ },
+});
+
+/**
+ * App 根组件
+ *
+ * 组件层级:
+ * 1. GestureHandlerRootView - 手势处理
+ * 2. SafeAreaProvider - 安全区域
+ * 3. QueryClientProvider - 数据请求
+ * 4. AppNavigator - 导航
+ */
+const App: React.FC = () => {
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+export default App;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..36eca32
--- /dev/null
+++ b/package.json
@@ -0,0 +1,67 @@
+/**
+ * Timeline 移动端 App - React Native 项目配置
+ *
+ * 功能描述:
+ * 定义 React Native 移动端应用的项目配置。
+ *
+ * 技术栈:
+ * - React Native 0.73+
+ * - TypeScript
+ * - React Navigation 6
+ * - Redux Toolkit / Zustand
+ * - React Query
+ * - NativeWind (TailwindCSS for RN)
+ *
+ * @author Timeline Team
+ * @date 2024
+ */
+
+// package.json 配置
+{
+ "name": "timeline-mobile",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "android": "react-native run-android",
+ "ios": "react-native run-ios",
+ "start": "react-native start",
+ "test": "jest",
+ "lint": "eslint .",
+ "type-check": "tsc --noEmit"
+ },
+ "dependencies": {
+ "react": "18.2.0",
+ "react-native": "0.73.0",
+ "@react-navigation/native": "^6.1.0",
+ "@react-navigation/native-stack": "^6.9.0",
+ "@react-navigation/bottom-tabs": "^6.5.0",
+ "@react-navigation/drawer": "^6.6.0",
+ "react-native-screens": "^3.29.0",
+ "react-native-safe-area-context": "^4.8.0",
+ "react-native-gesture-handler": "^2.14.0",
+ "react-native-reanimated": "^3.6.0",
+ "@react-native-async-storage/async-storage": "^1.21.0",
+ "@tanstack/react-query": "^5.17.0",
+ "zustand": "^4.4.0",
+ "axios": "^1.6.0",
+ "date-fns": "^3.2.0",
+ "react-native-image-picker": "^7.1.0",
+ "react-native-camera": "^4.2.1",
+ "react-native-geolocation-service": "^5.3.1",
+ "react-native-push-notification": "^8.1.1",
+ "@react-native-community/netinfo": "^11.3.0",
+ "nativewind": "^2.0.11",
+ "react-native-svg": "^14.1.0",
+ "react-native-fast-image": "^8.6.3"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.0",
+ "@types/react-native": "^0.73.0",
+ "typescript": "^5.3.0",
+ "eslint": "^8.56.0",
+ "@typescript-eslint/eslint-plugin": "^6.18.0",
+ "prettier": "^3.2.0",
+ "tailwindcss": "^3.4.0",
+ "react-native-svg-transformer": "^1.3.0"
+ }
+}
diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx
new file mode 100644
index 0000000..c9d4902
--- /dev/null
+++ b/src/navigation/AppNavigator.tsx
@@ -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();
+const Tab = createBottomTabNavigator();
+
+/**
+ * Tab 导航器
+ * 主应用的底部标签导航
+ */
+const TabNavigator: React.FC = () => {
+ return (
+
+ (
+
+ ),
+ }}
+ />
+ (
+
+ ),
+ }}
+ />
+ (
+
+ ),
+ }}
+ />
+ (
+
+ ),
+ }}
+ />
+
+ );
+};
+
+/**
+ * 认证导航器
+ * 未登录用户的认证流程
+ */
+const AuthStack: React.FC = () => {
+ return (
+
+
+
+
+ );
+};
+
+/**
+ * 主导航器
+ * 已登录用户的主应用流程
+ */
+const MainStack: React.FC = () => {
+ return (
+
+
+
+
+
+
+ );
+};
+
+/**
+ * 根导航器
+ * 根据登录状态切换认证/主应用流程
+ */
+const AppNavigator: React.FC = () => {
+ const { isAuthenticated } = useAuthStore();
+
+ return (
+
+
+ {isAuthenticated ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
+
+export default AppNavigator;
diff --git a/src/services/apiClient.ts b/src/services/apiClient.ts
new file mode 100644
index 0000000..013d8d4
--- /dev/null
+++ b/src/services/apiClient.ts
@@ -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;
diff --git a/src/stores/authStore.ts b/src/stores/authStore.ts
new file mode 100644
index 0000000..fd22204
--- /dev/null
+++ b/src/stores/authStore.ts
@@ -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) => void;
+ setToken: (token: string) => void;
+ setLoading: (loading: boolean) => void;
+}
+
+/**
+ * 认证状态 Store
+ */
+export const useAuthStore = create()(
+ 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,
+ }),
+ }
+ )
+);
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..c6f9b1e
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,32 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "commonjs",
+ "lib": ["es2017"],
+ "allowJs": true,
+ "jsx": "react-native",
+ "noEmit": true,
+ "isolatedModules": true,
+ "strict": true,
+ "moduleResolution": "node",
+ "baseUrl": "./",
+ "paths": {
+ "@/*": ["src/*"],
+ "@components/*": ["src/components/*"],
+ "@screens/*": ["src/screens/*"],
+ "@hooks/*": ["src/hooks/*"],
+ "@services/*": ["src/services/*"],
+ "@stores/*": ["src/stores/*"],
+ "@utils/*": ["src/utils/*"],
+ "@types/*": ["src/types/*"],
+ "@assets/*": ["src/assets/*"]
+ },
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "resolveJsonModule": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src/**/*", "index.js", "App.tsx"],
+ "exclude": ["node_modules", "babel.config.js", "metro.config.js", "jest.config.js"]
+}