山东大学软件学院项目实训小组进展记录8

React Native

一、 登录与注册功能实现

1. 技术栈架构

1.1 核心框架

  • React Native:跨平台移动应用开发框架,使用JavaScript/TypeScript构建iOS和Android应用
  • TypeScript:提供静态类型检查,增强代码健壮性和可维护性

1.2 关键依赖库

  • React Navigation:处理应用导航和路由管理
    • NativeStackNavigationProp:类型安全的导航属性
    • navigation.reset:用于登录后重置导航栈
  • AsyncStorage:数据持久化存储方案(用于本地存储和前端测试)
    • 存储token和用户ID
    • multiSet原子操作存储多个键值对
  • Fetch API:处理HTTP网络请求
    • 替代Axios的轻量级方案
    • 内置Promise支持

1.3 状态管理

  • React Hooks
    • useState:管理组件本地状态(表单输入、加载状态)
    • useLayoutEffect:处理界面布局相关副作用
  • Context API(可选):如需全局状态共享

2. 接口规范设计

2.1 端点管理

采用集中式API配置(API_CONFIG):

API_CONFIG.getUrl(API_CONFIG.ENDPOINTS.LOGIN) // 登录接口
API_CONFIG.getUrl(API_CONFIG.ENDPOINTS.REGISTER) // 注册接口 
API_CONFIG.getUrl(API_CONFIG.ENDPOINTS.CHANGE_PASSWORD(userId)) // 修改密码接口

2.2 请求规范

特性 登录 注册 修改密码
方法 POST POST PUT
Headers Content-Type: JSON Content-Type: JSON Content-Type: JSON + Authorization
Body {phone, password} {phone, password, name} {oldPassword, newPassword}

2.3 响应规范

  • 成功响应

    • 登录:200 OK + Authorization头(JWT) + X-User-ID头
    {"message": "登录成功"}
    
    • 注册:200 OK + 纯文本消息
    • 修改密码:200 OK + 纯文本消息
  • 错误响应

    • 登录:401(用户不存在)、500(服务器错误)
    • 注册:409(用户已存在)、500(服务器错误)
    • 修改密码:404(用户不存在)、400(原密码错误)

3. 功能实现详解

3.1 登录功能实现

3.1.1 界面组件(LoginScreen.tsx)
  • 视觉元素

    • ImageBackground:带背景图的登录界面
    • TextInput:手机号(数字键盘)和密码(安全输入)
    • TouchableOpacity:登录按钮带加载指示器
    • 导航链接:跳转注册页面
  • 状态管理

    const [phone, setPhone] = useState(''); // 手机号
    const [password, setPassword] = useState(''); // 密码
    const [loading, setLoading] = useState(false); // 加载状态
    
  • 核心流程

    1. 非空校验
    2. 调用authService.login
    3. 存储token和userId
    4. 导航到主界面
3.1.2 服务层(authService.ts)
export const login = async (phone: string, password: string) => {
  const response = await fetch(API_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ phone, password }),
  });
  
  // 错误处理
  if (!response.ok) {
    const errorData = await response.json();
    if (response.status === 401) throw new Error('用户不存在');
    if (response.status === 500) throw new Error('服务器错误');
    throw new Error(errorData.message || '登录失败');
  }
  
  // 成功处理
  const token = response.headers.get('Authorization')?.split(' ')[1] || '';
  const userId = response.headers.get('X-User-ID') || '';
  return { message: data.message, token, userId };
}

3.2 注册功能实现

3.2.1 服务层(authService.ts)
export const register = async (phone: string, password: string, name: string) => {
  const response = await fetch(API_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ phone, password, name }),
  });

  if (response.status === 409) throw new Error('用户已存在');
  if (response.status === 500) throw new Error('服务器错误');
  if (!response.ok) throw new Error('注册失败');
  
  return await response.text(); // 返回"注册成功"等文本
}

3.3 修改密码功能

export const changePassword = async (userId: string, oldPassword: string, newPassword: string) => {
  const token = await AsyncStorage.getItem('token');
  if (!token) throw new Error('用户未登录');

  const response = await fetch(API_URL, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify({ oldPassword, newPassword }),
  });

  if (response.status === 404) throw new Error('用户不存在');
  if (response.status === 400) throw new Error('原密码错误');
  if (response.status === 500) throw new Error('服务器错误');
  
  return await response.text() || '密码修改成功';
}

4. 安全增强措施

4.1 数据传输

  • 所有请求强制HTTPS
  • 敏感字段(password)在传输层加密

4.2 凭证存储

  • JWT token使用AsyncStorage安全存储
  • 实现自动令牌刷新机制(示例代码未展示)

4.3 输入防护

  • 密码字段启用secureTextEntry
  • 手机号字段限制keyboardType="phone-pad"

5. 用户体验优化

5.1 视觉反馈

  • 加载状态显示ActivityIndicator
  • 错误提示使用Alert.alert模态框
  • 输入框实时验证(示例中仅前端非空校验)

5.2 导航体验

// 登录成功后清除导航栈
navigation.reset({ index: 0, routes: [{ name: 'MainTabs' }] });

// 注册成功后建议跳转逻辑
navigation.navigate('Login', { registeredPhone: phone });

5.3 界面设计

  • 自适应布局:使用Flexbox和百分比宽度
  • 视觉层次:
    • 文字阴影(textShadow系列属性)
    • 输入框阴影效果(shadow系列属性)
    • 按钮点击反馈(TouchableOpacity)

6. 测试方案

6.1 开发阶段

// 模拟成功响应
const mockSuccessResponse = {
  json: async () => ({ message: "登录成功" }),
  ok: true,
  headers: {
    get: (header: string) => 
      header === "Authorization" ? "Bearer fake-token" : "123"
  }
}

// 模拟错误响应
const mockErrorResponse = {
  json: async () => ({ message: "无效凭证" }),
  ok: false,
  status: 401
}

6.2 测试用例

  1. 登录测试

    • 正确凭证→导航到主页+存储token
    • 错误密码→显示401错误
    • 空提交→显示前端验证错误
  2. 注册测试

    • 新手机号→返回成功消息
    • 已注册手机号→显示409冲突
    • 弱密码→后端验证失败

7. 扩展性设计

7.1

// 提取所有字符串为常量
const STRINGS = {
  LOGIN_FAILED: '登录失败',
  REGISTER_SUCCESS: '注册成功',
  // ...
}

7.2 APP主题

const themes = {
  light: {
    inputBg: '#ffffff',
    textColor: '#222222'
  },
  dark: {
    inputBg: '#333333',
    textColor: '#ffffff'
  }
}

7.3 API

// 统一API错误处理拦截器
const apiClient = async (endpoint: string, config: RequestInit) => {
  try {
    const response = await fetch(endpoint, config);
    if (!response.ok) throw new ApiError(response.status);
    return await response.json();
  } catch (error) {
    // 统一错误处理逻辑
  }
}

实现了React Native平台下完整的用户登陆注册认证流程:

  1. 技术架构:基于TypeScript的类型安全体系
  2. 接口规范:RESTful风格+JWT认证
  3. 功能实现:登录/注册/修改密码完整闭环
  4. 安全防护:从传输到存储的多层保护
  5. 用户体验:流畅的导航和视觉反馈

数据流:用户输入 → 前端验证 → API请求 → 响应处理 → 状态更新 → 界面反馈 → 导航跳转

二、用户资料管理功能实现

1. 整体架构与技术栈

1.1 技术栈

  • UI组件库
    • 核心组件:React Native 原生组件
    • 图标库:react-native-vector-icons/FontAwesome5

1.2 模块化设计

用户资料管理系统
├── 资料展示模块 (UserProfileScreen)
├── 资料编辑模块 (ProfileForm)
├── 密码修改模块 (ChangePasswordScreen)
├── 账号安全模块
│   ├── 退出登录功能
│   └── 账号注销功能
└── 服务层 (profileService, authService)

2. 核心功能实现细节

2.1 资料展示与编辑

数据流架构
用户界面 profileService 后端API AsyncStorage 用户 请求用户资料(getUserProfile) GET /user/profile 返回用户数据 缓存用户数据 返回资料数据 渲染资料卡片 提交修改(updateUserProfile) POST /user/profile 返回更新结果 更新缓存 返回操作结果 用户界面 profileService 后端API AsyncStorage 用户
关键代码实现
// 获取用户资料
const loadProfile = async () => {
  try {
    const userId = await AsyncStorage.getItem('userId');
    const data = await getUserProfile(userId);
    setProfile({
      name: data.name,
      gender: data.gender,
      birthYear: data.birthYear,
      occupation: data.occupation,
      height: data.height,
      healthDescription: data.healthDescription
    });
  } catch (err) {
    Alert.alert('加载失败', err.message);
  }
};

// 资料卡片组件
const InfoRow = ({ label, value }) => (
  <View style={styles.infoRow}>
    <Text style={styles.label}>{label}</Text>
    <Text style={styles.value}>{value || '--'}</Text>
  </View>
);

2.2 密码修改功能

安全验证流程
  1. 前端验证:
    • 密码长度 ≥ 8字符
    • 包含大小写字母
    • 包含数字
    • 两次输入一致
  2. 后端验证:
    • 当前密码正确性
    • 新密码复杂度
    • 防止近期密码重复
实现代码
const handleSubmit = async () => {
  // 前端验证
  const validationMsg = validatePassword(newPassword);
  if (validationMsg) return Alert.alert('密码不符合要求', validationMsg);
  if (newPassword !== confirmPassword) return Alert.alert('两次密码不一致');

  try {
    setIsLoading(true);
    const userId = await AsyncStorage.getItem('userId');
    await changePassword(userId, currentPassword, newPassword);
    
    Alert.alert('成功', '密码修改成功,请重新登录', [{
      text: '确定',
      onPress: () => navigation.reset({ index: 0, routes: [{ name: 'Login' }] })
    }]);
  } catch (err) {
    Alert.alert('修改失败', err.message);
  } finally {
    setIsLoading(false);
  }
};

2.3 账号安全操作

退出登录实现
const handleLogout = () => {
  Alert.alert('确认退出', '确定要退出登录吗?', [
    { text: '取消', style: 'cancel' },
    { 
      text: '退出', 
      onPress: () => {
        navigation.reset({ 
          index: 0, 
          routes: [{ name: 'Login' }] 
        });
      }
    },
  ]);
};
账号注销实现
const handleDelete = async () => {
  Alert.alert('确认注销', '账号信息将会永久删除', [
    { text: '取消', style: 'cancel' },
    {
      text: '确认删除',
      style: 'destructive',
      onPress: async () => {
        await deleteUser(userId);
        await AsyncStorage.multiRemove(['token', 'userId']);
        navigation.reset({ index: 0, routes: [{ name: 'Login' }] });
      }
    },
  ]);
};

3. 服务层设计

3.1 资料(profileService.ts)

export const getUserProfile = async (userId: string): Promise<UserProfile> => {
  try {
    const token = await AsyncStorage.getItem('token');
    const response = await fetch(API_CONFIG.getUrl(API_CONFIG.ENDPOINTS.GET_PROFILE(userId)), {
      headers: { 'Authorization': `Bearer ${token}` }
    });

    if (!response.ok) throw new Error('获取资料失败');
    
    const data = await response.json();
    await AsyncStorage.setItem(PROFILE_KEY, JSON.stringify(data)); // 缓存数据
    return data;
  } catch (error) {
    const cached = await AsyncStorage.getItem(PROFILE_KEY); // 降级读取缓存
    if (cached) return JSON.parse(cached);
    throw error;
  }
};

3.2 authService.ts

export const changePassword = async (userId: string, oldPassword: string, newPassword: string) => {
  const token = await AsyncStorage.getItem('token');
  const response = await fetch(API_CONFIG.getUrl(API_CONFIG.ENDPOINTS.CHANGE_PASSWORD(userId)), {
    method: 'PUT',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ oldPassword, newPassword })
  });

  if (response.status === 400) throw new Error('原密码错误');
  if (response.status === 404) throw new Error('用户不存在');
  if (!response.ok) throw new Error('修改密码失败');

  return await response.text();
};

4. UI 设计

4.1 视觉风格:

组件类别 样式特征 示例
卡片 白色背景+像素边框 borderWidth: 2
按钮 圆角+阴影+图标 borderRadius: 20
输入框 浅黄背景+像素边框 backgroundColor: #fef0d4
危险操作 红色警示色 backgroundColor: #DD4B4B

4.2 交互动效

  1. 键盘适应

    <KeyboardAvoidingView 
      behavior={Platform.OS === 'ios' ? 'padding' : undefined}
    >
      {/* 表单内容 */}
    </KeyboardAvoidingView>
    
  2. 加载状态

    <TouchableOpacity 
      disabled={isLoading}
      onPress={handleSubmit}
    >
      {isLoading ? <ActivityIndicator /> : <Text>提交</Text>}
    </TouchableOpacity>
    
  3. 表单验证反馈

    <TextInput
      onChangeText={(text) => {
        setNewPassword(text);
        setErrorMsg(validatePassword(text)); // 实时验证
      }}
    />
    {errorMsg && <Text style={styles.error}>{errorMsg}</Text>}
    

5. 安全增强

5.1 数据安全

措施 实现方式
密码传输加密 HTTPS + 请求体加密
Token 安全存储 AsyncStorage + 自动刷新机制
敏感操作确认 Alert二次确认
密码复杂度要求 前端实时验证+后端强制校验

5.2 密码策略验证

const validatePassword = (pwd) => {
  const rules = [
    { test: pwd.length >= 8, msg: '至少8个字符' },
    { test: /[A-Z]/.test(pwd), msg: '大写字母' },
    { test: /[a-z]/.test(pwd), msg: '小写字母' },
    { test: /[0-9]/.test(pwd), msg: '数字' }
  ];
  
  const failed = rules.filter(r => !r.test);
  return failed.length ? `需要包含: ${failed.map(r => r.msg).join(', ')}` : '';
};

6. 性能优化方案

6.1 数据缓存策略

// 优先网络请求,失败时降级读取缓存
try {
  const freshData = await fetchFreshData();
  await cacheData(freshData);
  return freshData;
} catch (error) {
  const cached = await getCachedData();
  return cached || Promise.reject(error);
}

6.2 渲染优化技巧

  1. 组件记忆化

    const InfoRow = React.memo(({ label, value }) => (
      <View style={styles.infoRow}>
        <Text>{label}</Text>
        <Text>{value}</Text>
      </View>
    ));
    
  2. 按需渲染

    const [visibleSections, setVisibleSections] = useState({
      basic: true,
      health: false
    });
    

7. 扩展性设计

7.1 多主题支持

const themes = {
  pixel: {
    primaryColor: '#75A6FF',
    borderStyle: { borderWidth: 2, borderColor: '#003366' }
  },
  modern: {
    primaryColor: '#6200ee',
    borderStyle: { borderWidth: 1, borderColor: '#e0e0e0' }
  }
};

7.2

const i18n = {
  en: {
    profileTitle: 'Profile',
    logoutConfirm: 'Are you sure to logout?'
  },
  zh: {
    profileTitle: '个人资料',
    logoutConfirm: '确定要退出登录吗?'
  }
};

const t = (key) => i18n[currentLanguage][key];

用户资料管理功能实现了:

  1. 完整功能闭环
    • 从资料展示、编辑到账号生命周期管理
    • 密码修改与安全退出流程
  2. 分层架构优势
    • 清晰的界面-服务-API分层
    • 可复用的服务模块设计
  3. 安全体系
    • 端到端的密码安全策略
    • 敏感操作确认机制
  4. 用户体验
    • 像素游戏化的视觉风格
    • 实时的表单验证反馈
    • 流畅的过渡动画

典型用户数据流:查看资料 → 编辑提交 → 密码修改 → 安全退出 → 重新登录

三、身体指标实现

1. 整体架构与技术栈

1.1 技术选型

技术/库 用途 应用场景
react-native-chart-kit 数据可视化 绘制血糖、血压、体重趋势图表
Axios/Fetch 网络请求 与后端API交互
AsyncStorage 本地数据存储 用户认证信息持久化

1.2 模块化设计

身体指标模块
├── BodyMetricsScreen.tsx       # 指标可视化主界面
│   ├── 趋势图表展示
│   ├── 历史数据列表
│   └── 导航到录入界面
├── BodyMetricsSubmitScreen.tsx # 指标录入/修改界面
│   ├── 表单输入
│   ├── 数据验证
│   └── 数据提交
└── metricService.ts            # 数据服务层
    ├── 数据获取
    ├── 数据提交
    └── 数据删除

2. 核心功能实现

2.1 指标可视化功能

数据获取与处理
// 获取用户历史指标数据
const loadMetrics = async () => {
  try {
    const userId = await AsyncStorage.getItem('userId');
    const history = await getUserBodyMetrics(userId);
    
    // 数据预处理
    const processedData = history.map(item => ({
      ...item,
      date: formatDate(item.date) // 统一日期格式
    }));
    
    setMetricsHistory(processedData);
  } catch (error) {
    Alert.alert('获取失败', error.message);
  }
};

// 图表数据准备
const prepareChartData = (metricType: 'fbg'|'pbg'|'weight') => {
  return {
    labels: metricsHistory.map(item => item.date),
    datasets: [{
      data: metricsHistory.map(item => item[metricType]),
      color: () => getColorByMetric(metricType), // 根据指标类型返回不同颜色
      strokeWidth: 2
    }]
  };
};
多图表展示实现
<ScrollView>
  {/* 空腹血糖图表 */}
  <LineChart
    data={prepareChartData('fbg')}
    width={screenWidth - 32}
    height={220}
    yAxisSuffix=" mmol/L"
    chartConfig={chartConfig}
    style={styles.chart}
  />
  
  {/* 血压双线图表 */}
  <LineChart
    data={{
      labels: metricsHistory.map(item => item.date),
      datasets: [
        { data: metricsHistory.map(item => item.sbp), ...sbpStyle },
        { data: metricsHistory.map(item => item.dbp), ...dbpStyle }
      ]
    }}
    width={screenWidth - 32}
    height={220}
    yAxisSuffix=" mmHg"
  />
</ScrollView>

2.2 指标录入与修改功能

表单管理与验证
// 表单状态管理
const [formData, setFormData] = useState<BodyMetrics>({
  fbg: '',
  pbg: '',
  sbp: '',
  dbp: '',
  weight: ''
});

// 表单验证
const validateForm = () => {
  const rules = [
    { field: 'fbg', validator: v => v === '' || (v >= 2 && v <= 20), message: '空腹血糖值异常' },
    { field: 'sbp', validator: v => v === '' || (v >= 60 && v <= 250), message: '收缩压异常' }
  ];

  return rules.every(rule => {
    const isValid = rule.validator(formData[rule.field]);
    if (!isValid) Alert.alert('输入错误', rule.message);
    return isValid;
  });
};

// 输入处理
const handleInputChange = (field: keyof BodyMetrics, value: string) => {
  setFormData(prev => ({
    ...prev,
    [field]: value.replace(/[^0-9.]/g, '') // 只允许数字和小数点
  }));
};
数据提交逻辑
const handleSubmit = async () => {
  if (!validateForm()) return;
  
  try {
    const payload = {
      userId: await AsyncStorage.getItem('userId'),
      date: new Date().toISOString().split('T')[0],
      ...Object.fromEntries(
        Object.entries(formData).map(([k,v]) => [k, v === '' ? undefined : parseFloat(v)])
    };
    
    await saveBodyMetrics(payload);
    Alert.alert('成功', '数据已保存', [{
      text: '确定',
      onPress: () => navigation.goBack()
    }]);
  } catch (error) {
    Alert.alert('提交失败', error.message);
  }
};

3. 服务层设计

3.1 API服务封装

// 获取用户历史数据
export const getUserBodyMetrics = async (userId: string): Promise<BodyMetrics[]> => {
  const response = await api.get<BodyMetrics[]>(
    API_CONFIG.getUrl(API_CONFIG.ENDPOINTS.BODY_METRIC_GET_USER(userId))
  );
  return response.data;
};

// 提交指标数据
export const saveBodyMetrics = async (data: BodyMetrics): Promise<void> => {
  await api.post(
    API_CONFIG.getUrl(API_CONFIG.ENDPOINTS.BODY_METRIC_SAVE),
    data
  );
};

// 数据类型定义
export interface BodyMetrics {
  entryId?: number;
  userId: string;
  date: string;
  fbg?: number;  // 空腹血糖(mmol/L)
  pbg?: number;  // 餐后血糖(mmol/L)
  sbp?: number;  // 收缩压(mmHg)
  dbp?: number;  // 舒张压(mmHg)
  weight?: number; // 体重(kg)
}

4. UI优化设计

4.1 可视化界面优化

  • 响应式图表:根据屏幕尺寸自动调整图表宽度
const screenWidth = Dimensions.get('window').width;
<LineChart width={screenWidth - 32} ... />
  • 图表交互:添加点击事件显示详细数据
<LineChart
  onDataPointClick={({value, index}) => 
    Alert.alert(`详细数据`, `${metricsHistory[index].date}: ${value}`)
  }
/>

4.2 表单界面优化

  • 输入优化:针对不同指标提供专用键盘
<TextInput
  keyboardType="decimal-pad"
  placeholder="空腹血糖(mmol/L)"
  value={formData.fbg}
  onChangeText={v => handleInputChange('fbg', v)}
/>
  • 保存按钮状态:根据输入变化动态更新
<TouchableOpacity
  style={[styles.button, !hasChanges && styles.disabledButton]}
  disabled={!hasChanges}
  onPress={handleSubmit}
>
  <Text>保存数据</Text>
</TouchableOpacity>

5. 安全与性能

5.1 数据安全

  • 传输安全:所有API请求通过HTTPS加密
  • 敏感数据处理:本地不存储指标数据,实时从服务端获取
  • 输入过滤:防止SQL注入和XSS攻击
const safeInput = input.replace(/[^0-9.]/g, '');

5.2 性能优化

  • 数据缓存:使用内存缓存减少重复请求
const [cachedData, setCachedData] = useState<BodyMetrics[]>([]);

// 获取数据时优先检查缓存
if (cachedData.length > 0) return cachedData;
const freshData = await fetchData();
setCachedData(freshData);
  • 虚拟列表:长列表使用FlatList优化
<FlatList
  data={metricsHistory}
  keyExtractor={item => item.date}
  renderItem={({item}) => <MetricItem data={item} />}
/>

6. 扩展功能

6.1 多日期选择

const [selectedDate, setSelectedDate] = useState(new Date());

<DatePicker
  current={selectedDate}
  onDateChange={setSelectedDate}
/>

6.2 数据导出

const exportToCSV = () => {
  const csv = metricsHistory.map(item => 
    `${item.date},${item.fbg},${item.weight}`
  ).join('\n');
  shareFile(csv, 'health_metrics.csv');
};

7. 错误处理

7.1 错误边界处理

try {
  await saveBodyMetrics(data);
} catch (error) {
  if (error.response?.status === 401) {
    navigation.navigate('Login');
  } else {
    logError(error);
    showToast('保存失败,请重试');
  }
}

7.2 操作日志

const logMetricUpdate = (action: string, data: BodyMetrics) => {
  console.log(`[${new Date().toISOString()}] ${action}:`, {
    userId: data.userId,
    date: data.date,
    fields: Object.keys(data).filter(k => data[k] !== undefined)
  });
};

实现了完整的身体指标管理功能:

  1. 可视化

    • 多维度健康指标图表展示
    • 支持交互式数据探索
  2. 数据管理

    • 类型安全的数据处理
    • 完善的数据验证机制
  3. 扩展性

    • 支持新指标快速接入
    • 易于添加新功能模块

数据流:用户录入指标 → 系统验证数据 → 提交到服务端 → 可视化展示历史趋势 → 发现健康变化规律

四、设置与反馈页面实现

1. 技术架构

技术/库 用途 应用场景
ImageBackground 背景设计 页面整体视觉风格

2. 设计亮点

2.1 胶囊形状设计实现

// 胶囊按钮样式
const capsuleStyle = {
  borderRadius: 25,  // 高圆角形成胶囊形状
  paddingHorizontal: 20,
  paddingVertical: 10,
  backgroundColor: '#428DCB',
  shadowColor: '#000',
  shadowOffset: { width: 0, height: 2 },
  shadowOpacity: 0.25,
  elevation: 3
}

// 应用示例
<TouchableOpacity style={capsuleStyle}>
  <Text style={{color: 'white'}}>提交反馈</Text>
</TouchableOpacity>

2.2 图标系统集成

// 图标配置方案
const settingIcons = {
  theme: 'palette',
  notification: 'bell',
  language: 'language',
  feedback: 'message-square'
}

// 使用示例
<Icon 
  name={settingIcons.theme} 
  size={24} 
  color={isDarkTheme ? '#f5dd4b' : '#333'} 
/>

3. 用户体验增强

3.1 交互反馈

交互类型 实现方式 视觉反馈
按钮点击 TouchableOpacity 按压透明度变化
开关切换 Switch组件 滑块动画+颜色变化
表单提交 ActivityIndicator 加载旋转图标

3.2 状态管理优化

// 设置页面状态管理
const [settings, setSettings] = useState({
  theme: 'light',
  notifications: true,
  language: 'zh-CN'
});

// 持久化存储
useEffect(() => {
  const loadSettings = async () => {
    const saved = await AsyncStorage.getItem('userSettings');
    if (saved) setSettings(JSON.parse(saved));
  };
  loadSettings();
}, []);

const updateSetting = (key, value) => {
  const newSettings = {...settings, [key]: value};
  setSettings(newSettings);
  AsyncStorage.setItem('userSettings', JSON.stringify(newSettings));
};

4. 关键实现

4.1 设置页面功能架构

设置页面
主题切换
通知开关
语言选择
反馈入口
本地存储

4.2 反馈页面核心逻辑

const FeedbackScreen = () => {
  const [feedback, setFeedback] = useState('');
  const [status, setStatus] = useState<'idle'|'submitting'|'success'|'error'>('idle');

  const handleSubmit = async () => {
    if (!feedback.trim()) {
      Alert.alert('提示', '请输入有效反馈内容');
      return;
    }

    setStatus('submitting');
    
    try {
      // 模拟网络请求
      await new Promise(resolve => setTimeout(resolve, 1500));
      setStatus('success');
      setFeedback('');
    } catch {
      setStatus('error');
    }
  };

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        placeholder="您的宝贵意见..."
        value={feedback}
        onChangeText={setFeedback}
        multiline
      />
      
      <TouchableOpacity 
        style={[
          styles.submitButton,
          status === 'submitting' && styles.disabledButton
        ]}
        onPress={handleSubmit}
        disabled={status === 'submitting'}
      >
        {status === 'submitting' ? (
          <ActivityIndicator color="white" />
        ) : (
          <Text style={styles.buttonText}>提交反馈</Text>
        )}
      </TouchableOpacity>

      {status === 'success' && (
        <Text style={styles.successText}>感谢您的反馈!</Text>
      )}
    </View>
  );
};

5. 视觉与交互

5.1 动态主题支持

// 主题上下文
const ThemeContext = createContext({
  theme: 'light',
  toggleTheme: () => {}
});

// 主题切换实现
const ThemeProvider = ({children}) => {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    AsyncStorage.setItem('appTheme', newTheme);
  };

  return (
    <ThemeContext.Provider value={{theme, toggleTheme}}>
      {children}
    </ThemeContext.Provider>
  );
};

5.2 微交互

元素 交互效果 实现方式
按钮 按压缩小动画 Animated API
输入框 聚焦边框高亮 onFocus/onBlur事件
开关控件 平滑滑动过渡 Switch组件的trackColor

6. 响应式布局

6.1 设备适配

// 响应式尺寸计算
const {width, height} = Dimensions.get('window');
const responsiveSize = (size: number) => {
  const guidelineWidth = 375; // 设计稿基准宽度
  return (size * width) / guidelineWidth;
};

// 应用示例
const styles = StyleSheet.create({
  container: {
    padding: responsiveSize(20),
  },
  input: {
    height: responsiveSize(150),
  }
});

6.2 横竖屏适配

// 屏幕方向监听
const [orientation, setOrientation] = useState(
  Dimensions.get('window').width > Dimensions.get('window').height 
    ? 'landscape' 
    : 'portrait'
);

useEffect(() => {
  const subscription = Dimensions.addEventListener('change', ({window}) => {
    setOrientation(window.width > window.height ? 'landscape' : 'portrait');
  });
  return () => subscription?.remove();
}, []);

7. 实现亮点

  1. 视觉一致性:通过胶囊设计语言和统一图标系统建立识别度
  2. 交互友好性:交互和状态反馈提升操作体验
  3. 性能优化:本地存储+内存缓存减少不必要的重渲染
  4. 扩展性:模块化设计方便新增设置项和反馈类型

数据流:用户打开设置页 → 加载本地偏好 → 修改设置 → 实时保存 → 视觉反馈;用户提交反馈 → 前端验证 → 模拟请求 → 结果提示。

五、首页功能实现

1.1 获取用户信息

首页首先需要展示用户的基本信息(如用户名、年龄、身高、身体描述等)。我们通过调用后端接口 getUserProfile 来获取这些数据。

步骤:

  1. 使用 AsyncStorage.getItem 获取存储在本地的用户 ID(userId)。
  2. 调用 getUserProfile 服务函数,传递 userId 来从后端获取用户资料。
  3. 获取成功后,使用 useState 钩子更新页面中的状态(例如:setUsernamesetBirthYearsetHeightsetHealthDesc)。

相关代码:

// 从AsyncStorage获取用户ID
const userId = await AsyncStorage.getItem('userId');
if (!userId) {
  Alert.alert('错误', '无法获取用户ID,请重新登录');
  return;
}

// 调用后端接口获取用户资料
const profile = await getUserProfile(userId);
if (profile) {
  setUsername(profile.name || '');
  setBirthYear(profile.birthYear || null);
  setHeight(profile.height || null);
  setHealthDesc(profile.healthDescription || '');
}

1.2 获取今日锻炼和用药计划

我们需要获取用户当天的锻炼和用药计划。通过调用 getTodayExercisePlansgetTodayMedicinePlans 函数从后端获取当天的计划。

步骤:

  1. useFocusEffect 中,确保每次页面获取焦点时都会调用后端 API 来获取当天的计划。
  2. 使用 setTodayExercisesetTodayMedicine 分别将锻炼和用药计划保存到组件的状态中。

相关代码:

const ex = await getTodayExercisePlans(userId); // 获取锻炼计划
const med = await getTodayMedicinePlans(userId); // 获取用药计划
setTodayExercise(ex); // 更新锻炼计划
setTodayMedicine(med); // 更新用药计划

1.3 删除计划

提供删除按钮,用户可以删除当天已完成的锻炼或用药计划。点击删除按钮后,调用相应的删除函数 (deleteExercisePlandeleteMedicinePlan) 来删除计划,并在删除后刷新页面,确保显示的数据是最新的。

步骤:

  1. 用户点击删除按钮时,会触发 handleDelete 函数。
  2. 根据传入的 typeexercisemedicine)调用对应的删除 API。
  3. 删除成功后,调用 setTodayExercisesetTodayMedicine 来更新页面上的计划。

相关代码:

const handleDelete = async (id: string, type: 'exercise' | 'medicine') => {
  try {
    if (type === 'exercise') {
      await deleteExercisePlan(id);
      setTodayExercise(prev => prev.filter(item => item.id !== id)); // 删除本地锻炼计划
    } else {
      await deleteMedicinePlan(id);
      setTodayMedicine(prev => prev.filter(item => item.id !== id)); // 删除本地用药计划
    }
  } catch (error) {
    Alert.alert('错误', '删除计划失败');
  }
};

六、 计划填写功能实现

1.1 填写并提交锻炼计划

用户可以在页面中输入锻炼计划的描述,点击提交按钮后,锻炼计划会被发送到后端进行生成,并保存在后端数据库中。

步骤:

  1. 用户在输入框中填写锻炼计划的描述。
  2. 点击提交按钮后,调用 generateExercisePlans 函数将描述提交到后端,后端根据描述生成新的锻炼计划。
  3. 提交成功后,调用 fetchAllPlans 刷新页面上的计划列表,显示最新的锻炼计划。

相关代码:

const handleSubmitExercise = async () => {
  if (!userId) return Alert.alert('错误', '请先登录');
  if (!exerciseDescription) return Alert.alert('提示', '请输入描述');
  setExerciseLoading(true);
  try {
    // 调用后端生成锻炼计划
    await generateExercisePlans(userId, exerciseDescription);
    await fetchAllPlans(); // 刷新锻炼计划列表
    setExerciseDescription(''); // 清空输入框
    Alert.alert('成功', '锻炼计划已生成');
  } catch (error) {
    Alert.alert('生成失败', error.message);
  } finally {
    setExerciseLoading(false);
  }
};

1.2 填写并提交用药计划

用户也可以在输入框中填写用药计划,提交后,后端会根据描述生成用药计划,并将其保存到数据库中。

步骤:

  1. 用户填写用药计划的描述并点击提交按钮。
  2. 调用 generateMedicinePlans 函数将描述发送到后端,生成用药计划。
  3. 提交成功后,刷新计划列表,显示最新的用药计划。

相关代码:

const handleSubmitMedicine = async () => {
  if (!userId) return Alert.alert('错误', '请先登录');
  if (!medicineDescription) return Alert.alert('提示', '请输入用药信息');
  setMedicineLoading(true);
  try {
    // 调用后端生成用药计划
    await generateMedicinePlans(userId, medicineDescription);
    await fetchAllPlans(); // 刷新用药计划列表
    setMedicineDescription(''); // 清空输入框
    Alert.alert('成功', '用药计划已生成');
  } catch (error) {
    Alert.alert('生成失败', error.message);
  } finally {
    setMedicineLoading(false);
  }
};

1.3 刷新计划列表

在提交计划(锻炼计划或用药计划)后,使用 fetchAllPlans 函数来刷新页面上的计划列表,确保页面显示的是最新的计划数据。

步骤:

  1. 调用 fetchAllPlans 从后端获取最新的锻炼和用药计划。
  2. 使用 setExercisePlanssetMedicinePlans 更新页面上的状态,重新渲染列表。

相关代码:

const fetchAllPlans = async () => {
  try {
    setRefreshing(true);
    const [exercises, medications] = await Promise.all([
      getExercisePlans(userId),
      getMedicinePlans(userId)
    ]);
    setExercisePlans(exercises); // 更新锻炼计划
    setMedicinePlans(medications); // 更新用药计划
  } catch (error) {
    Alert.alert('获取计划失败', error.message);
  } finally {
    setRefreshing(false);
  }
};

七、 饮食建议功能实现

1.1 上传食物图片

用户可以选择食物图片进行上传,上传后会进行分析并返回健康建议。

步骤:

  1. 使用 launchImageLibrary 打开图库选择图片。通过设置合适的 options(例如:最大宽度、最大高度、图片质量等)来控制上传图片的质量。
  2. 选择图片后,更新状态 setImageAsset 来保存选中的图片。

相关代码:

const pickImage = () => {
  const options = {
    mediaType: 'photo',
    maxWidth: 1024,
    maxHeight: 1024,
    quality: 0.8,
  };
  launchImageLibrary(options, (res) => {
    if (res.didCancel) {
      console.log('用户取消了图片选择');
    } else if (res.errorCode) {
      console.log('图片选择错误:', res.errorMessage);
    } else if (res.assets && res.assets.length > 0) {
      const asset = res.assets[0];
      setImageAsset({
        uri: asset.uri,
        type: asset.type || 'image/jpeg',
        name: asset.fileName || 'image.jpg',
      });
    }
  });
};

1.2 获取健康建议

上传食物图片后,调用后端接口 analyzeFoodImage 来分析图片内容,并返回健康建议。

步骤:

  1. 调用 analyzeFoodImage 函数并传递图片文件,函数返回食物分析结果。
  2. 使用 setResponse 将分析结果保存到状态中,并在页面中渲染建议内容。

相关代码:

const handleSubmit = async () => {
  if (!imageAsset) {
    Alert.alert('提示', '请上传食物图片');
    return;
  }
  try {
    setLoading(true);
    const result = await analyzeFoodImage(imageAsset); // 调用后端接口分析图片
    setResponse(result); // 更新健康建议
  } catch (err) {
    Alert.alert('错误', '食物分析失败,请稍后重试');
  } finally {
    setLoading(false);
  }
};

1.3 显示健康建议

将从后端获取到的健康建议渲染到页面上,使用 Markdown 组件来格式化显示结果,提供良好的用户体验。

步骤:

  1. 使用 Markdown 组件来渲染返回的健康建议文本。
  2. 样式自定义:根据需求定制 Markdown 组件的样式,使得展示结果更加友好。

相关代码:

{response && (
  <View style={styles.responseBox}>
    <Text style={styles.responseTitle}>健康分析与建议</Text>
    <Markdown
      style={{
        body: styles.markdownBody,
        heading1: styles.markdownH1,
        heading2: styles.markdownH2,
        heading3: styles.markdownH3,
        _item: styles.markdownListItem,
        text: styles.markdownText,
        strong: styles.markdownStrong,
      }}
    >
      {response}
    </Markdown>
  </View>
)}

八AI助手功能实现

1.1 角色选择与发送消息功能

用户选择角色后,通过角色提示,发送消息给AI并显示AI的回答。

步骤:

  1. 角色选择:用户点击按钮选择角色,保存角色到状态中。
  2. 发送消息:用户输入内容,选择角色,发送消息并显示结果。

相关代码:

const handleRoleSelect = (role: string) => {
  setSelectedRole(role); // 设置选中的角色
  setShowOptions(false); // 关闭角色选择菜单
};

const handleSend = async () => {
  if (!userInput.trim()) return;
  const rolePrompt = selectedRole ? `请以${selectedRole}的身份回答:` : '';
  const newMessages = [...messages, { role: 'user', text: rolePrompt + userInput }];
  setMessages(newMessages);
  try {
    const aiResponse = await sendMessageToAI({ message: newMessages, withRAG: false, prompt: rolePrompt });
    setMessages([...newMessages, { role: 'ai', text: aiResponse }]);
  } catch (error) {
    console.error('发送失败:', error);
    setMessages([...newMessages, { role: 'ai', text: '⚠️ 出现错误,请稍后重试。' }]);
  }
};

1.2 消息输入与显示

用户输入消息后,发送给AI并显示回复,支持Markdown渲染。

步骤:

  1. 消息输入:用户输入后,点击发送按钮,消息发送并显示。
  2. 消息显示:用户的消息和AI的回复分别显示,并使用Markdown渲染AI的消息。

相关代码:

{messages.map((msg, index) => (
  <View key={index} style={{ marginBottom: 10 }}>
    <View style={{ flexDirection: 'row', alignItems: 'flex-start' }}>
      {msg.role !== 'user' && <Image source={require('../assets/deepseek.png')} style={{ width: 30, height: 30 }} />}
      <View style={{ flex: 1 }}>
        <View style={[styles.messageContainer, msg.role === 'user' ? styles.userMessageContainer : styles.aiMessageContainer]}>
          {msg.role === 'ai' ? <Markdown>{msg.text}</Markdown> : <Text style={styles.messageText}>{msg.text}</Text>}
        </View>
      </View>
      {msg.role === 'user' && <Image source={require('../assets/bigmax.jpg')} style={{ width: 30, height: 30 }} />}
    </View>
  </View>
))}

1.3 快速输入与键盘适配

提供了快速输入选项,用户可直接选择预设问题,输入框根据键盘状态自适应。

步骤:

  1. 快速输入:用户选择预设问题,自动填充到输入框。
  2. 键盘适配:根据键盘显示状态调整输入框位置。

相关代码:

useEffect(() => {
  const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => setKeyboardOn(true));
  const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => setKeyboardOn(false));
  return () => {
    keyboardDidHideListener.remove();
    keyboardDidShowListener.remove();
  };
}, []);

<TouchableWithoutFeedback onPress={() => setSecondMenu('')}>
  <View style={[isKeyboardOn ? styles.fastInputContainerOn : styles.fastInputContainer]}>
    <Button title={'症状咨询'} onPress={() => setSecondMenu('a')} />
    <Button title={'健康管理'} onPress={() => setSecondMenu('b')} />
  </View>
</TouchableWithoutFeedback>

九、应用导航与页面切换

该功能实现了基于 React Navigation 的页面切换。主要分为两部分:

  1. 堆栈导航:控制不同页面间的跳转。
  2. 底部Tab导航:提供主界面功能模块的快速访问。
  3. 登录检查:在应用启动时,根据用户是否已登录决定显示登录页面还是直接进入主界面。

1.1 登录检查

在应用启动时,我们需要检查本地存储(AsyncStorage)中是否有用户信息。如果有用户信息,则表示用户已登录,直接进入主界面;如果没有,跳转到登录页面。

相关代码:

// 检查用户是否已登录
const checkLoginStatus = async () => {
  const user = await AsyncStorage.getItem('user'); // 从本地获取用户信息
  setIsLoggedIn(user !== null); // 如果有用户信息,说明已登录
};

1.2 堆栈导航

使用 createNativeStackNavigator 来实现堆栈导航,将各个页面按顺序组织。登录页面、注册页面等都作为堆栈的一部分,用户可以通过堆栈导航进行切换。

相关代码:

// 堆栈导航 - 登录或主界面选择
return (
  <NavigationContainer>
    <Stack.Navigator initialRouteName={isLoggedIn ? "MainTabs" : "Login"}>
      <Stack.Screen name="Login" component={LoginScreen} options={{ title: '登录' }} />
      {/* 其他页面 */}
      <Stack.Screen name="MainTabs" component={BottomTabNavigator} options={{ headerShown: false }} />
    </Stack.Navigator>
  </NavigationContainer>
);

1.3 底部Tab导航

一旦用户登录成功,就会跳转到包含底部导航栏的主界面。createBottomTabNavigator 用于实现底部的导航标签,用户可以快速访问不同功能模块,如首页、身体指标、计划填写等。

相关代码:

// 底部Tab导航 - 主界面功能模块
export default function BottomTabNavigator() {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ color, size }) => {
          let iconName = 'home';
          if (route.name === 'Dashboard') iconName = 'home';
          else if (route.name === 'BodyMetrics') iconName = 'fitness';
          else if (route.name === 'PlanForm') iconName = 'calendar';
          else if (route.name === 'ChatWithAI') iconName = 'chatbubble-ellipses';
          else if (route.name === 'FoodAdvisor') iconName = 'restaurant';
          return <Ionicons name={iconName} size={size} color={color} />;
        },
        tabBarActiveTintColor: '#2196F3', // 激活时颜色
        tabBarInactiveTintColor: 'gray', // 非激活时颜色
      })}
    >
      <Tab.Screen name="Dashboard" component={DashboardScreen} options={{ title: '首页' }} />
      <Tab.Screen name="BodyMetrics" component={BodyMetricsScreen} options={{ title: '身体指标' }} />
      <Tab.Screen name="PlanForm" component={PlanFormScreen} options={{ title: '计划填写' }} />
      <Tab.Screen name="ChatWithAI" component={ChatWithAIScreen} options={{ title: 'AI助手' }} />
      <Tab.Screen name="FoodAdvisor" component={FoodAdvisorScreen} options={{ title: '饮食建议' }} />
    </Tab.Navigator>
  );
}

你可能感兴趣的:(项目实训,react,native,人工智能)