在现代移动应用开发中,网络请求和数据处理是核心功能之一。UniApp 提供了丰富的 API 来处理 HTTP 请求、数据缓存、状态管理等功能。本章将详细介绍如何在 UniApp 中进行网络请求、数据缓存、状态管理以及实时通信。

5.1 HTTP 请求

5.1.1 uni.request() API 详解

uni.request() 是 UniApp 中发起网络请求的核心 API,支持 GET、POST、PUT、DELETE 等 HTTP 方法。

基础用法

// GET 请求
uni.request({
  url: 'https://api.example.com/users',
  method: 'GET',
  success: (res) => {
    console.log('请求成功:', res.data);
  },
  fail: (err) => {
    console.error('请求失败:', err);
  },
  complete: () => {
    console.log('请求完成');
  }
});

// POST 请求
uni.request({
  url: 'https://api.example.com/users',
  method: 'POST',
  data: {
    name: '张三',
    email: 'zhangsan@example.com'
  },
  header: {
    'Content-Type': 'application/json'
  },
  success: (res) => {
    console.log('创建成功:', res.data);
  }
});

Promise 封装

// utils/request.js
class HttpRequest {
  constructor(baseURL = '') {
    this.baseURL = baseURL;
    this.timeout = 10000;
    this.header = {
      'Content-Type': 'application/json'
    };
  }

  // 基础请求方法
  request(options) {
    return new Promise((resolve, reject) => {
      // 处理 URL
      let url = options.url;
      if (!url.startsWith('http')) {
        url = this.baseURL + url;
      }

      // 合并请求头
      const header = {
        ...this.header,
        ...options.header
      };

      // 添加 token
      const token = uni.getStorageSync('token');
      if (token) {
        header.Authorization = `Bearer ${token}`;
      }

      uni.request({
        url,
        method: options.method || 'GET',
        data: options.data,
        header,
        timeout: options.timeout || this.timeout,
        success: (res) => {
          if (res.statusCode === 200) {
            resolve(res.data);
          } else {
            reject(new Error(`HTTP ${res.statusCode}: ${res.data.message || '请求失败'}`));
          }
        },
        fail: (err) => {
          reject(new Error(err.errMsg || '网络请求失败'));
        }
      });
    });
  }

  // GET 请求
  get(url, params = {}) {
    // 处理查询参数
    const queryString = Object.keys(params)
      .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
      .join('&');
    
    if (queryString) {
      url += (url.includes('?') ? '&' : '?') + queryString;
    }

    return this.request({ url, method: 'GET' });
  }

  // POST 请求
  post(url, data = {}) {
    return this.request({ url, method: 'POST', data });
  }

  // PUT 请求
  put(url, data = {}) {
    return this.request({ url, method: 'PUT', data });
  }

  // DELETE 请求
  delete(url) {
    return this.request({ url, method: 'DELETE' });
  }

  // 文件上传
  upload(url, filePath, formData = {}) {
    return new Promise((resolve, reject) => {
      const token = uni.getStorageSync('token');
      const header = {};
      if (token) {
        header.Authorization = `Bearer ${token}`;
      }

      uni.uploadFile({
        url: this.baseURL + url,
        filePath,
        name: 'file',
        formData,
        header,
        success: (res) => {
          try {
            const data = JSON.parse(res.data);
            resolve(data);
          } catch (e) {
            resolve(res.data);
          }
        },
        fail: reject
      });
    });
  }

  // 文件下载
  download(url, savePath) {
    return new Promise((resolve, reject) => {
      uni.downloadFile({
        url: this.baseURL + url,
        success: (res) => {
          if (res.statusCode === 200) {
            resolve(res.tempFilePath);
          } else {
            reject(new Error('下载失败'));
          }
        },
        fail: reject
      });
    });
  }
}

// 创建实例
const http = new HttpRequest('https://api.example.com');

export default http;

使用示例

// pages/user/user.vue
<template>
  <view class="user-page">
    <view class="user-list">
      <view v-for="user in users" :key="user.id" class="user-item">
        <text>{{ user.name }}</text>
        <text>{{ user.email }}</text>
      </view>
    </view>
    
    <button @click="loadUsers">加载用户</button>
    <button @click="createUser">创建用户</button>
  </view>
</template>

<script>
import http from '@/utils/request.js';

export default {
  data() {
    return {
      users: []
    };
  },
  
  async onLoad() {
    await this.loadUsers();
  },
  
  methods: {
    // 加载用户列表
    async loadUsers() {
      try {
        uni.showLoading({ title: '加载中...' });
        
        const response = await http.get('/users', {
          page: 1,
          limit: 10
        });
        
        this.users = response.data;
        
      } catch (error) {
        console.error('加载用户失败:', error);
        uni.showToast({
          title: '加载失败',
          icon: 'error'
        });
      } finally {
        uni.hideLoading();
      }
    },
    
    // 创建用户
    async createUser() {
      try {
        const response = await http.post('/users', {
          name: '新用户',
          email: 'newuser@example.com'
        });
        
        this.users.push(response.data);
        
        uni.showToast({
          title: '创建成功',
          icon: 'success'
        });
        
      } catch (error) {
        console.error('创建用户失败:', error);
        uni.showToast({
          title: '创建失败',
          icon: 'error'
        });
      }
    }
  }
};  
</script>

5.3.2 Pinia 在 UniApp 中的使用

Pinia 是 Vue 3 推荐的状态管理库,相比 Vuex 更加简洁和类型友好。

安装和配置 Pinia

npm install pinia
// main.js
import { createSSRApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

export function createApp() {
  const app = createSSRApp(App);
  const pinia = createPinia();
  
  app.use(pinia);
  
  return {
    app
  };
}

用户 Store

// stores/user.js
import { defineStore } from 'pinia';
import { AuthManager } from '@/utils/secure-storage';
import http from '@/utils/http';

const authManager = new AuthManager();

export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: null,
    token: null,
    isLoggedIn: false,
    permissions: [],
    roles: []
  }),
  
  getters: {
    // 检查权限
    hasPermission: (state) => (permission) => {
      return state.permissions.includes(permission);
    },
    
    // 检查角色
    hasRole: (state) => (role) => {
      return state.roles.includes(role);
    },
    
    // 用户头像
    avatar: (state) => {
      return state.userInfo?.avatar || '/static/default-avatar.png';
    },
    
    // 用户昵称
    nickname: (state) => {
      return state.userInfo?.nickname || '未设置昵称';
    }
  },
  
  actions: {
    // 登录
    async login(username, password, rememberMe = false) {
      try {
        const result = await authManager.login(username, password, rememberMe);
        
        this.token = result.token;
        this.userInfo = result.userInfo;
        this.isLoggedIn = true;
        
        // 获取用户权限
        await this.getUserPermissions();
        
        return result;
      } catch (error) {
        throw error;
      }
    },
    
    // 登出
    async logout() {
      try {
        await http.post('/auth/logout');
      } catch (error) {
        console.error('登出接口调用失败:', error);
      } finally {
        authManager.logout();
        this.$reset(); // Pinia 提供的重置状态方法
      }
    },
    
    // 获取用户信息
    async getUserInfo() {
      try {
        if (!this.token) {
          throw new Error('未登录');
        }
        
        const response = await http.get('/user/info');
        this.userInfo = response.data;
        uni.setStorageSync('userInfo', this.userInfo);
        
        return this.userInfo;
      } catch (error) {
        console.error('获取用户信息失败:', error);
        throw error;
      }
    },
    
    // 获取用户权限
    async getUserPermissions() {
      try {
        if (!this.token) {
          return;
        }
        
        const response = await http.get('/user/permissions');
        const { permissions, roles } = response.data;
        
        this.permissions = permissions;
        this.roles = roles;
        
      } catch (error) {
        console.error('获取用户权限失败:', error);
      }
    },
    
    // 初始化用户状态
    initUserState() {
      const token = authManager.getToken();
      const userInfo = uni.getStorageSync('userInfo');
      
      if (token && userInfo) {
        this.token = token;
        this.userInfo = userInfo;
        this.isLoggedIn = true;
        
        // 获取最新权限
        this.getUserPermissions();
      }
    },
    
    // 更新用户信息
    async updateUserInfo(userInfo) {
      try {
        const response = await http.put('/user/info', userInfo);
        this.userInfo = response.data;
        uni.setStorageSync('userInfo', this.userInfo);
        
        return this.userInfo;
      } catch (error) {
        console.error('更新用户信息失败:', error);
        throw error;
      }
    }
  }
});

购物车 Store

// stores/cart.js
import { defineStore } from 'pinia';
import http from '@/utils/http';
import { useUserStore } from './user';

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: []
  }),
  
  getters: {
    // 总数量
    totalCount: (state) => {
      return state.items.reduce((total, item) => total + item.quantity, 0);
    },
    
    // 总价格
    totalPrice: (state) => {
      return state.items.reduce((total, item) => total + (item.price * item.quantity), 0);
    },
    
    // 获取商品数量
    getItemCount: (state) => (productId) => {
      const item = state.items.find(item => item.productId === productId);
      return item ? item.quantity : 0;
    },
    
    // 检查商品是否在购物车中
    isInCart: (state) => (productId) => {
      return state.items.some(item => item.productId === productId);
    },
    
    // 选中的商品
    selectedItems: (state) => {
      return state.items.filter(item => item.selected);
    },
    
    // 选中商品的总价
    selectedTotalPrice: (state) => {
      return state.items
        .filter(item => item.selected)
        .reduce((total, item) => total + (item.price * item.quantity), 0);
    }
  },
  
  actions: {
    // 添加到购物车
    addToCart(product, quantity = 1) {
      const existingItem = this.items.find(item => item.productId === product.id);
      
      if (existingItem) {
        existingItem.quantity += quantity;
      } else {
        this.items.push({
          productId: product.id,
          productName: product.name,
          productImage: product.image,
          price: product.price,
          quantity,
          selected: true
        });
      }
      
      this.saveCartToStorage();
      
      uni.showToast({
        title: '已添加到购物车',
        icon: 'success'
      });
    },
    
    // 从购物车移除
    removeFromCart(productId) {
      const index = this.items.findIndex(item => item.productId === productId);
      if (index > -1) {
        this.items.splice(index, 1);
      }
      this.saveCartToStorage();
    },
    
    // 更新数量
    updateQuantity(productId, quantity) {
      const item = this.items.find(item => item.productId === productId);
      if (item) {
        if (quantity <= 0) {
          this.removeFromCart(productId);
        } else {
          item.quantity = quantity;
        }
      }
      this.saveCartToStorage();
    },
    
    // 切换选中状态
    toggleSelected(productId) {
      const item = this.items.find(item => item.productId === productId);
      if (item) {
        item.selected = !item.selected;
      }
      this.saveCartToStorage();
    },
    
    // 全选/取消全选
    toggleSelectAll(selected) {
      this.items.forEach(item => {
        item.selected = selected;
      });
      this.saveCartToStorage();
    },
    
    // 清空购物车
    clearCart() {
      this.items = [];
      this.saveCartToStorage();
    },
    
    // 删除选中商品
    removeSelectedItems() {
      this.items = this.items.filter(item => !item.selected);
      this.saveCartToStorage();
    },
    
    // 保存购物车到本地存储
    saveCartToStorage() {
      uni.setStorageSync('cart', this.items);
    },
    
    // 从本地存储加载购物车
    loadCartFromStorage() {
      const cartItems = uni.getStorageSync('cart') || [];
      this.items = cartItems;
    },
    
    // 同步购物车到服务器
    async syncCartToServer() {
      try {
        const userStore = useUserStore();
        if (!userStore.isLoggedIn) {
          return;
        }
        
        await http.post('/cart/sync', {
          items: this.items
        });
        
      } catch (error) {
        console.error('同步购物车失败:', error);
      }
    },
    
    // 从服务器加载购物车
    async loadCartFromServer() {
      try {
        const userStore = useUserStore();
        if (!userStore.isLoggedIn) {
          return;
        }
        
        const response = await http.get('/cart');
        this.items = response.data.items || [];
        this.saveCartToStorage();
        
      } catch (error) {
        console.error('加载购物车失败:', error);
      }
    }
  }
});

应用 Store

// stores/app.js
import { defineStore } from 'pinia';

export const useAppStore = defineStore('app', {
  state: () => ({
    theme: 'light',
    language: 'zh-CN',
    systemInfo: null,
    networkType: 'unknown',
    loading: false,
    tabBarBadge: {
      cart: 0,
      message: 0
    }
  }),
  
  getters: {
    isDarkMode: (state) => state.theme === 'dark',
    isIOS: (state) => state.systemInfo && state.systemInfo.platform === 'ios',
    isOnline: (state) => state.networkType !== 'none',
    
    // 状态栏高度
    statusBarHeight: (state) => {
      return state.systemInfo?.statusBarHeight || 0;
    },
    
    // 安全区域
    safeArea: (state) => {
      return state.systemInfo?.safeArea || {};
    }
  },
  
  actions: {
    // 切换主题
    toggleTheme() {
      this.theme = this.theme === 'light' ? 'dark' : 'light';
      uni.setStorageSync('theme', this.theme);
    },
    
    // 设置主题
    setTheme(theme) {
      this.theme = theme;
      uni.setStorageSync('theme', theme);
    },
    
    // 设置语言
    setLanguage(language) {
      this.language = language;
      uni.setStorageSync('language', language);
    },
    
    // 设置加载状态
    setLoading(loading) {
      this.loading = loading;
    },
    
    // 获取系统信息
    getSystemInfo() {
      return new Promise((resolve) => {
        uni.getSystemInfo({
          success: (res) => {
            this.systemInfo = res;
            resolve(res);
          }
        });
      });
    },
    
    // 获取网络类型
    getNetworkType() {
      return new Promise((resolve) => {
        uni.getNetworkType({
          success: (res) => {
            this.networkType = res.networkType;
            resolve(res);
          }
        });
      });
    },
    
    // 监听网络状态变化
    watchNetworkStatus() {
      uni.onNetworkStatusChange((res) => {
        this.networkType = res.networkType;
      });
    },
    
    // 设置 TabBar 角标
    setTabBarBadge(tab, count) {
      this.tabBarBadge[tab] = count;
      
      const tabIndexMap = {
        home: 0,
        category: 1,
        cart: 2,
        profile: 3
      };
      
      const index = tabIndexMap[tab];
      if (index !== undefined) {
        if (count > 0) {
          uni.setTabBarBadge({
            index,
            text: count.toString()
          });
        } else {
          uni.removeTabBarBadge({ index });
        }
      }
    },
    
    // 清除 TabBar 角标
    clearTabBarBadge(tab) {
      this.setTabBarBadge(tab, 0);
    },
    
    // 初始化应用状态
    async initAppState() {
      // 加载主题
      const theme = uni.getStorageSync('theme') || 'light';
      this.theme = theme;
      
      // 加载语言
      const language = uni.getStorageSync('language') || 'zh-CN';
      this.language = language;
      
      // 获取系统信息
      await this.getSystemInfo();
      
      // 获取网络状态
      await this.getNetworkType();
      
      // 监听网络状态变化
      this.watchNetworkStatus();
    }
  }
});

在组件中使用 Pinia

// pages/index/index.vue
<template>
  <view class="home-page" :class="{ 'dark-mode': isDarkMode }">
    <view class="user-info" v-if="userStore.isLoggedIn">
      <image :src="userStore.avatar" class="avatar" />
      <text>欢迎,{{ userStore.nickname }}</text>
      <button @click="userStore.logout">退出登录</button>
    </view>
    
    <view class="cart-info">
      <text>购物车商品数量:{{ cartStore.totalCount }}</text>
      <text>总价:¥{{ cartStore.totalPrice.toFixed(2) }}</text>
      <button @click="addToCart">添加商品</button>
    </view>
    
    <view class="theme-switcher">
      <button @click="appStore.toggleTheme">
        切换到{{ isDarkMode ? '浅色' : '深色' }}主题
      </button>
    </view>
    
    <view class="network-status">
      <text>网络状态:{{ appStore.networkType }}</text>
      <text :class="{ online: appStore.isOnline, offline: !appStore.isOnline }">
        {{ appStore.isOnline ? '在线' : '离线' }}
      </text>
    </view>
  </view>
</template>

<script setup>
import { computed } from 'vue';
import { useUserStore } from '@/stores/user';
import { useCartStore } from '@/stores/cart';
import { useAppStore } from '@/stores/app';

// 使用 stores
const userStore = useUserStore();
const cartStore = useCartStore();
const appStore = useAppStore();

// 计算属性
const isDarkMode = computed(() => appStore.isDarkMode);

// 方法
const addToCart = () => {
  const product = {
    id: Date.now(),
    name: '测试商品',
    price: 99.99,
    image: '/static/product.jpg'
  };
  
  cartStore.addToCart(product, 1);
  
  // 更新购物车角标
  appStore.setTabBarBadge('cart', cartStore.totalCount);
};

// 生命周期
uni.onLoad(() => {
  // 初始化状态
  userStore.initUserState();
  cartStore.loadCartFromStorage();
  appStore.initAppState();
});
</script>

<style>
.home-page {
  padding: 20px;
  background-color: #fff;
}

.dark-mode {
  background-color: #1a1a1a;
  color: #fff;
}

.user-info {
  display: flex;
  align-items: center;
  margin-bottom: 20px;
}

.avatar {
  width: 50px;
  height: 50px;
  border-radius: 25px;
  margin-right: 10px;
}

.cart-info {
  margin-bottom: 20px;
}

.network-status .online {
  color: #4CAF50;
}

.network-status .offline {
  color: #F44336;
}
</style>

5.3.3 数据持久化方案

// utils/persistence.js
class PersistenceManager {
  constructor() {
    this.stores = new Map();
  }

  // 注册需要持久化的 store
  register(storeName, store, options = {}) {
    const config = {
      key: options.key || storeName,
      include: options.include || null, // 包含的字段
      exclude: options.exclude || null, // 排除的字段
      transform: options.transform || null, // 数据转换函数
      ...options
    };
    
    this.stores.set(storeName, { store, config });
    
    // 立即加载持久化数据
    this.loadStore(storeName);
    
    // 监听状态变化并自动保存
    if (store.$subscribe) {
      store.$subscribe((mutation, state) => {
        this.saveStore(storeName);
      });
    }
  }

  // 保存 store 状态
  saveStore(storeName) {
    const storeData = this.stores.get(storeName);
    if (!storeData) return;
    
    const { store, config } = storeData;
    let dataToSave = { ...store.$state };
    
    // 处理包含字段
    if (config.include && Array.isArray(config.include)) {
      const filteredData = {};
      config.include.forEach(key => {
        if (key in dataToSave) {
          filteredData[key] = dataToSave[key];
        }
      });
      dataToSave = filteredData;
    }
    
    // 处理排除字段
    if (config.exclude && Array.isArray(config.exclude)) {
      config.exclude.forEach(key => {
        delete dataToSave[key];
      });
    }
    
    // 数据转换
    if (config.transform && typeof config.transform.save === 'function') {
      dataToSave = config.transform.save(dataToSave);
    }
    
    try {
      uni.setStorageSync(config.key, dataToSave);
    } catch (error) {
      console.error(`保存 ${storeName} 状态失败:`, error);
    }
  }

  // 加载 store 状态
  loadStore(storeName) {
    const storeData = this.stores.get(storeName);
    if (!storeData) return;
    
    const { store, config } = storeData;
    
    try {
      let savedData = uni.getStorageSync(config.key);
      if (!savedData) return;
      
      // 数据转换
      if (config.transform && typeof config.transform.load === 'function') {
        savedData = config.transform.load(savedData);
      }
      
      // 合并到 store 状态
      Object.keys(savedData).forEach(key => {
        if (key in store.$state) {
          store.$state[key] = savedData[key];
        }
      });
      
    } catch (error) {
      console.error(`加载 ${storeName} 状态失败:`, error);
    }
  }

  // 清除 store 持久化数据
  clearStore(storeName) {
    const storeData = this.stores.get(storeName);
    if (!storeData) return;
    
    const { config } = storeData;
    
    try {
      uni.removeStorageSync(config.key);
    } catch (error) {
      console.error(`清除 ${storeName} 状态失败:`, error);
    }
  }

  // 清除所有持久化数据
  clearAll() {
    this.stores.forEach((_, storeName) => {
      this.clearStore(storeName);
    });
  }
}

// 创建持久化管理器实例
const persistence = new PersistenceManager();

// 使用示例
// 在 main.js 中配置持久化
export function setupPersistence() {
  const userStore = useUserStore();
  const cartStore = useCartStore();
  const appStore = useAppStore();
  
  // 用户状态持久化(排除敏感信息)
  persistence.register('user', userStore, {
    exclude: ['token'], // 不持久化 token
    transform: {
      save: (data) => {
        // 保存前的数据处理
        return {
          ...data,
          lastSaveTime: Date.now()
        };
      },
      load: (data) => {
        // 加载后的数据处理
        const { lastSaveTime, ...restData } = data;
        
        // 检查数据是否过期(7天)
        if (Date.now() - lastSaveTime > 7 * 24 * 60 * 60 * 1000) {
          return {}; // 返回空对象,不加载过期数据
        }
        
        return restData;
      }
    }
  });
  
  // 购物车状态持久化
  persistence.register('cart', cartStore);
  
  // 应用状态持久化(只保存主题和语言)
  persistence.register('app', appStore, {
    include: ['theme', 'language']
  });
}

export default persistence;

5.4 接口封装

5.4.1 统一的 API 管理

// api/index.js
import http from '@/utils/http';

// 用户相关接口
export const userApi = {
  // 登录
  login: (data) => http.post('/auth/login', data),
  
  // 注册
  register: (data) => http.post('/auth/register', data),
  
  // 登出
  logout: () => http.post('/auth/logout'),
  
  // 获取用户信息
  getUserInfo: () => http.get('/user/info'),
  
  // 更新用户信息
  updateUserInfo: (data) => http.put('/user/info', data),
  
  // 修改密码
  changePassword: (data) => http.put('/user/password', data),
  
  // 获取用户权限
  getUserPermissions: () => http.get('/user/permissions'),
  
  // 上传头像
  uploadAvatar: (filePath) => http.upload('/user/avatar', filePath)
};

// 商品相关接口
export const productApi = {
  // 获取商品列表
  getProductList: (params) => http.get('/products', params),
  
  // 获取商品详情
  getProductDetail: (id) => http.get(`/products/${id}`),
  
  // 搜索商品
  searchProducts: (keyword, params = {}) => http.get('/products/search', { keyword, ...params }),
  
  // 获取商品分类
  getCategories: () => http.get('/categories'),
  
  // 获取热门商品
  getHotProducts: (limit = 10) => http.get('/products/hot', { limit }),
  
  // 获取推荐商品
  getRecommendProducts: (userId) => http.get(`/products/recommend/${userId}`)
};

// 购物车相关接口
export const cartApi = {
  // 获取购物车
  getCart: () => http.get('/cart'),
  
  // 添加到购物车
  addToCart: (data) => http.post('/cart/add', data),
  
  // 更新购物车商品数量
  updateCartItem: (id, quantity) => http.put(`/cart/items/${id}`, { quantity }),
  
  // 删除购物车商品
  removeCartItem: (id) => http.delete(`/cart/items/${id}`),
  
  // 清空购物车
  clearCart: () => http.delete('/cart'),
  
  // 同步购物车
  syncCart: (items) => http.post('/cart/sync', { items })
};

// 订单相关接口
export const orderApi = {
  // 创建订单
  createOrder: (data) => http.post('/orders', data),
  
  // 获取订单列表
  getOrderList: (params) => http.get('/orders', params),
  
  // 获取订单详情
  getOrderDetail: (id) => http.get(`/orders/${id}`),
  
  // 取消订单
  cancelOrder: (id) => http.put(`/orders/${id}/cancel`),
  
  // 确认收货
  confirmOrder: (id) => http.put(`/orders/${id}/confirm`),
  
  // 申请退款
  refundOrder: (id, reason) => http.post(`/orders/${id}/refund`, { reason }),
  
  // 获取物流信息
  getLogistics: (id) => http.get(`/orders/${id}/logistics`)
};

// 地址相关接口
export const addressApi = {
  // 获取地址列表
  getAddressList: () => http.get('/addresses'),
  
  // 添加地址
  addAddress: (data) => http.post('/addresses', data),
  
  // 更新地址
  updateAddress: (id, data) => http.put(`/addresses/${id}`, data),
  
  // 删除地址
  deleteAddress: (id) => http.delete(`/addresses/${id}`),
  
  // 设置默认地址
  setDefaultAddress: (id) => http.put(`/addresses/${id}/default`)
};

// 支付相关接口
export const paymentApi = {
  // 创建支付订单
  createPayment: (data) => http.post('/payments', data),
  
  // 查询支付状态
  getPaymentStatus: (id) => http.get(`/payments/${id}/status`),
  
  // 获取支付方式
  getPaymentMethods: () => http.get('/payments/methods')
};

// 文件上传接口
export const uploadApi = {
  // 上传图片
  uploadImage: (filePath, formData = {}) => http.upload('/upload/image', filePath, formData),
  
  // 上传文件
  uploadFile: (filePath, formData = {}) => http.upload('/upload/file', filePath, formData),
  
  // 批量上传
  batchUpload: (filePaths) => {
    const uploadPromises = filePaths.map(filePath => 
      uploadApi.uploadImage(filePath)
    );
    return Promise.all(uploadPromises);
  }
};

// 系统相关接口
export const systemApi = {
  // 获取系统配置
  getConfig: () => http.get('/system/config'),
  
  // 获取版本信息
  getVersion: () => http.get('/system/version'),
  
  // 意见反馈
  feedback: (data) => http.post('/system/feedback', data),
  
  // 获取公告
  getNotices: () => http.get('/system/notices'),
  
  // 获取帮助文档
  getHelp: () => http.get('/system/help')
};

// 统计相关接口
export const analyticsApi = {
  // 页面访问统计
  trackPageView: (page) => http.post('/analytics/pageview', { page }),
  
  // 事件统计
  trackEvent: (event, data = {}) => http.post('/analytics/event', { event, data }),
  
  // 错误统计
  trackError: (error) => http.post('/analytics/error', { error })
};

5.4.2 请求和响应的标准化处理

// utils/api-helper.js
class ApiHelper {
  constructor() {
    this.defaultConfig = {
      showLoading: true,
      showError: true,
      loadingText: '请求中...',
      timeout: 10000
    };
  }

  // 标准化请求
  async request(apiFunction, params = {}, config = {}) {
    const finalConfig = { ...this.defaultConfig, ...config };
    
    try {
      // 显示加载状态
      if (finalConfig.showLoading) {
        uni.showLoading({
          title: finalConfig.loadingText,
          mask: true
        });
      }
      
      // 发起请求
      const response = await apiFunction(params);
      
      // 标准化响应数据
      return this.normalizeResponse(response);
      
    } catch (error) {
      // 错误处理
      if (finalConfig.showError) {
        this.handleError(error);
      }
      throw error;
    } finally {
      // 隐藏加载状态
      if (finalConfig.showLoading) {
        uni.hideLoading();
      }
    }
  }

  // 标准化响应数据
  normalizeResponse(response) {
    // 假设后端返回格式为 { code, data, message }
    if (response.code === 200) {
      return {
        success: true,
        data: response.data,
        message: response.message || '操作成功'
      };
    } else {
      throw new Error(response.message || '请求失败');
    }
  }

  // 错误处理
  handleError(error) {
    let message = '请求失败';
    
    if (error.message) {
      message = error.message;
    } else if (error.errMsg) {
      message = error.errMsg;
    }
    
    uni.showToast({
      title: message,
      icon: 'error',
      duration: 2000
    });
  }

  // 分页请求
  async paginate(apiFunction, params = {}, config = {}) {
    const defaultParams = {
      page: 1,
      limit: 10,
      ...params
    };
    
    const response = await this.request(apiFunction, defaultParams, config);
    
    return {
      ...response,
      pagination: {
        current: defaultParams.page,
        pageSize: defaultParams.limit,
        total: response.data.total || 0,
        hasMore: (defaultParams.page * defaultParams.limit) < (response.data.total || 0)
      }
    };
  }

  // 批量请求
  async batch(requests, config = {}) {
    const finalConfig = { ...this.defaultConfig, ...config };
    
    try {
      if (finalConfig.showLoading) {
        uni.showLoading({
          title: '批量请求中...',
          mask: true
        });
      }
      
      const results = await Promise.allSettled(requests.map(req => 
        this.request(req.api, req.params, { ...req.config, showLoading: false, showError: false })
      ));
      
      return results.map((result, index) => ({
        success: result.status === 'fulfilled',
        data: result.status === 'fulfilled' ? result.value : null,
        error: result.status === 'rejected' ? result.reason : null,
        request: requests[index]
      }));
      
    } finally {
      if (finalConfig.showLoading) {
        uni.hideLoading();
      }
    }
  }

  // 重试请求
  async retry(apiFunction, params = {}, config = {}) {
    const retryConfig = {
      maxRetries: 3,
      retryDelay: 1000,
      ...config.retry
    };
    
    let lastError;
    
    for (let i = 0; i <= retryConfig.maxRetries; i++) {
      try {
        return await this.request(apiFunction, params, {
          ...config,
          showLoading: i === 0 // 只在第一次请求时显示loading
        });
      } catch (error) {
        lastError = error;
        
        if (i < retryConfig.maxRetries) {
          await this.delay(retryConfig.retryDelay * Math.pow(2, i));
        }
      }
    }
    
    throw lastError;
  }

  // 延迟函数
  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// 创建 API 助手实例
const apiHelper = new ApiHelper();

// 使用示例
export class ProductService {
  // 获取商品列表
  static async getProductList(params = {}) {
    return await apiHelper.paginate(
      (p) => productApi.getProductList(p),
      params,
      { loadingText: '加载商品中...' }
    );
  }
  
  // 获取商品详情
  static async getProductDetail(id) {
    return await apiHelper.request(
      () => productApi.getProductDetail(id),
      {},
      { loadingText: '加载详情中...' }
    );
  }
  
  // 搜索商品(带重试)
  static async searchProducts(keyword, params = {}) {
    return await apiHelper.retry(
      (p) => productApi.searchProducts(keyword, p),
      params,
      {
        loadingText: '搜索中...',
        retry: { maxRetries: 2, retryDelay: 500 }
      }
    );
  }
  
  // 批量获取商品信息
  static async batchGetProducts(productIds) {
    const requests = productIds.map(id => ({
      api: () => productApi.getProductDetail(id),
      params: {},
      config: {}
    }));
    
    return await apiHelper.batch(requests, {
      loadingText: '批量加载中...'
    });
  }
}

export default apiHelper;

5.5 实时通信

5.5.1 WebSocket 连接管理

// utils/websocket.js
class WebSocketManager {
  constructor() {
    this.socket = null;
    this.url = '';
    this.protocols = [];
    this.isConnected = false;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
    this.reconnectInterval = 3000;
    this.heartbeatInterval = 30000;
    this.heartbeatTimer = null;
    this.reconnectTimer = null;
    this.listeners = new Map();
    this.messageQueue = [];
  }

  // 连接 WebSocket
  connect(url, protocols = []) {
    this.url = url;
    this.protocols = protocols;
    
    return new Promise((resolve, reject) => {
      try {
        this.socket = uni.connectSocket({
          url: this.url,
          protocols: this.protocols,
          success: () => {
            console.log('WebSocket 连接成功');
          },
          fail: (error) => {
            console.error('WebSocket 连接失败:', error);
            reject(error);
          }
        });
        
        this.setupEventListeners(resolve, reject);
        
      } catch (error) {
        console.error('WebSocket 连接异常:', error);
        reject(error);
      }
    });
  }

  // 设置事件监听器
  setupEventListeners(resolve, reject) {
    // 连接打开
    uni.onSocketOpen(() => {
      console.log('WebSocket 连接已打开');
      this.isConnected = true;
      this.reconnectAttempts = 0;
      
      // 发送队列中的消息
      this.flushMessageQueue();
      
      // 开始心跳
      this.startHeartbeat();
      
      // 触发连接成功事件
      this.emit('open');
      
      resolve();
    });
    
    // 接收消息
    uni.onSocketMessage((res) => {
      try {
        const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
        this.handleMessage(data);
      } catch (error) {
        console.error('解析 WebSocket 消息失败:', error);
        this.emit('error', error);
      }
    });
    
    // 连接关闭
    uni.onSocketClose((res) => {
      console.log('WebSocket 连接已关闭:', res);
      this.isConnected = false;
      this.stopHeartbeat();
      
      this.emit('close', res);
      
      // 自动重连
      if (this.reconnectAttempts < this.maxReconnectAttempts) {
        this.scheduleReconnect();
      }
    });
    
    // 连接错误
    uni.onSocketError((error) => {
      console.error('WebSocket 连接错误:', error);
      this.emit('error', error);
      reject(error);
    });
  }

  // 处理接收到的消息
  handleMessage(data) {
    // 处理心跳响应
    if (data.type === 'pong') {
      return;
    }
    
    // 触发消息事件
    this.emit('message', data);
    
    // 根据消息类型触发特定事件
    if (data.type) {
      this.emit(data.type, data);
    }
  }

  // 发送消息
  send(data) {
    const message = typeof data === 'string' ? data : JSON.stringify(data);
    
    if (this.isConnected) {
      uni.sendSocketMessage({
        data: message,
        success: () => {
          console.log('消息发送成功:', message);
        },
        fail: (error) => {
          console.error('消息发送失败:', error);
          this.emit('error', error);
        }
      });
    } else {
      // 连接未建立时,将消息加入队列
      this.messageQueue.push(message);
    }
  }

  // 发送队列中的消息
  flushMessageQueue() {
    while (this.messageQueue.length > 0) {
      const message = this.messageQueue.shift();
      this.send(message);
    }
  }

  // 关闭连接
  close() {
    this.stopHeartbeat();
    this.clearReconnectTimer();
    
    if (this.socket) {
      uni.closeSocket();
      this.socket = null;
    }
    
    this.isConnected = false;
    this.reconnectAttempts = this.maxReconnectAttempts; // 阻止自动重连
  }

  // 开始心跳
  startHeartbeat() {
    this.stopHeartbeat();
    
    this.heartbeatTimer = setInterval(() => {
      if (this.isConnected) {
        this.send({ type: 'ping', timestamp: Date.now() });
      }
    }, this.heartbeatInterval);
  }

  // 停止心跳
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }

  // 安排重连
  scheduleReconnect() {
    this.clearReconnectTimer();
    
    const delay = this.reconnectInterval * Math.pow(2, this.reconnectAttempts);
    
    console.log(`${delay}ms 后尝试第 ${this.reconnectAttempts + 1} 次重连`);
    
    this.reconnectTimer = setTimeout(() => {
      this.reconnectAttempts++;
      this.connect(this.url, this.protocols).catch(() => {
        // 重连失败,继续尝试
      });
    }, delay);
  }

  // 清除重连定时器
  clearReconnectTimer() {
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
  }

  // 添加事件监听器
  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(callback);
  }

  // 移除事件监听器
  off(event, callback) {
    if (this.listeners.has(event)) {
      const callbacks = this.listeners.get(event);
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
      }
    }
  }

  // 触发事件
  emit(event, data) {
    if (this.listeners.has(event)) {
      this.listeners.get(event).forEach(callback => {
        try {
          callback(data);
        } catch (error) {
          console.error(`事件 ${event} 的回调函数执行失败:`, error);
        }
      });
    }
  }

  // 获取连接状态
  getReadyState() {
    return this.isConnected ? 1 : 0; // 1: OPEN, 0: CONNECTING/CLOSED
  }
}

// 创建 WebSocket 管理器实例
const wsManager = new WebSocketManager();

export default wsManager;

5.5.2 聊天系统实现

// utils/chat.js
import wsManager from './websocket';
import { useUserStore } from '@/stores/user';

class ChatManager {
  constructor() {
    this.currentRoom = null;
    this.messages = new Map(); // roomId -> messages[]
    this.unreadCounts = new Map(); // roomId -> count
    this.listeners = new Map();
    this.isInitialized = false;
  }

  // 初始化聊天管理器
  async initialize() {
    if (this.isInitialized) return;
    
    const userStore = useUserStore();
    if (!userStore.isLoggedIn) {
      throw new Error('用户未登录');
    }
    
    // 连接 WebSocket
    const wsUrl = `wss://api.example.com/chat?token=${userStore.token}`;
    await wsManager.connect(wsUrl);
    
    // 设置消息监听器
    this.setupMessageListeners();
    
    this.isInitialized = true;
  }

  // 设置消息监听器
  setupMessageListeners() {
    // 接收新消息
    wsManager.on('message', (data) => {
      this.handleIncomingMessage(data);
    });
    
    // 消息状态更新
    wsManager.on('message_status', (data) => {
      this.handleMessageStatus(data);
    });
    
    // 用户状态更新
    wsManager.on('user_status', (data) => {
      this.handleUserStatus(data);
    });
    
    // 房间事件
    wsManager.on('room_event', (data) => {
      this.handleRoomEvent(data);
    });
  }

  // 处理接收到的消息
  handleIncomingMessage(data) {
    const { roomId, message } = data;
    
    // 添加消息到本地存储
    this.addMessage(roomId, message);
    
    // 更新未读计数
    if (roomId !== this.currentRoom) {
      this.incrementUnreadCount(roomId);
    }
    
    // 触发消息事件
    this.emit('new_message', { roomId, message });
    
    // 显示通知
    if (roomId !== this.currentRoom) {
      this.showMessageNotification(message);
    }
  }

  // 处理消息状态更新
  handleMessageStatus(data) {
    const { roomId, messageId, status } = data;
    
    const messages = this.messages.get(roomId) || [];
    const message = messages.find(msg => msg.id === messageId);
    
    if (message) {
      message.status = status;
      this.emit('message_status_updated', { roomId, messageId, status });
    }
  }

  // 处理用户状态更新
  handleUserStatus(data) {
    this.emit('user_status_updated', data);
  }

  // 处理房间事件
  handleRoomEvent(data) {
    this.emit('room_event', data);
  }

  // 加入房间
  joinRoom(roomId) {
    this.currentRoom = roomId;
    
    // 清除未读计数
    this.clearUnreadCount(roomId);
    
    // 发送加入房间消息
    wsManager.send({
      type: 'join_room',
      roomId: roomId
    });
    
    this.emit('room_joined', { roomId });
  }

  // 离开房间
  leaveRoom() {
    if (this.currentRoom) {
      wsManager.send({
        type: 'leave_room',
        roomId: this.currentRoom
      });
      
      this.emit('room_left', { roomId: this.currentRoom });
      this.currentRoom = null;
    }
  }

  // 发送消息
  sendMessage(roomId, content, type = 'text') {
    const message = {
      id: this.generateMessageId(),
      roomId,
      content,
      type,
      timestamp: Date.now(),
      status: 'sending'
    };
    
    // 添加到本地消息列表
    this.addMessage(roomId, message);
    
    // 发送到服务器
    wsManager.send({
      type: 'send_message',
      message
    });
    
    this.emit('message_sent', { roomId, message });
    
    return message;
  }

  // 发送图片消息
  async sendImageMessage(roomId, filePath) {
    try {
      // 先发送一个占位消息
      const message = this.sendMessage(roomId, '正在发送图片...', 'image');
      
      // 上传图片
      const uploadResult = await this.uploadImage(filePath);
      
      // 更新消息内容
      message.content = uploadResult.url;
      message.status = 'sent';
      
      this.emit('message_updated', { roomId, message });
      
      return message;
    } catch (error) {
      console.error('发送图片失败:', error);
      throw error;
    }
  }

  // 上传图片
  uploadImage(filePath) {
    return new Promise((resolve, reject) => {
      uni.uploadFile({
        url: 'https://api.example.com/upload/image',
        filePath,
        name: 'file',
        success: (res) => {
          const data = JSON.parse(res.data);
          if (data.code === 200) {
            resolve(data.data);
          } else {
            reject(new Error(data.message));
          }
        },
        fail: reject
      });
    });
  }

  // 添加消息
  addMessage(roomId, message) {
    if (!this.messages.has(roomId)) {
      this.messages.set(roomId, []);
    }
    
    const messages = this.messages.get(roomId);
    
    // 检查消息是否已存在
    const existingIndex = messages.findIndex(msg => msg.id === message.id);
    if (existingIndex > -1) {
      messages[existingIndex] = message;
    } else {
      messages.push(message);
      // 按时间排序
      messages.sort((a, b) => a.timestamp - b.timestamp);
    }
  }

  // 获取房间消息
  getRoomMessages(roomId) {
    return this.messages.get(roomId) || [];
  }

  // 增加未读计数
  incrementUnreadCount(roomId) {
    const current = this.unreadCounts.get(roomId) || 0;
    this.unreadCounts.set(roomId, current + 1);
    this.emit('unread_count_updated', { roomId, count: current + 1 });
  }

  // 清除未读计数
  clearUnreadCount(roomId) {
    this.unreadCounts.set(roomId, 0);
    this.emit('unread_count_updated', { roomId, count: 0 });
  }

  // 获取未读计数
  getUnreadCount(roomId) {
    return this.unreadCounts.get(roomId) || 0;
  }

  // 显示消息通知
  showMessageNotification(message) {
    // 在应用前台时显示简单提示
    uni.showToast({
      title: `新消息: ${message.content}`,
      icon: 'none',
      duration: 2000
    });
  }

  // 生成消息 ID
  generateMessageId() {
    return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  // 添加事件监听器
  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(callback);
  }

  // 移除事件监听器
  off(event, callback) {
    if (this.listeners.has(event)) {
      const callbacks = this.listeners.get(event);
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
      }
    }
  }

  // 触发事件
  emit(event, data) {
    if (this.listeners.has(event)) {
      this.listeners.get(event).forEach(callback => {
        try {
          callback(data);
        } catch (error) {
          console.error(`聊天事件 ${event} 的回调函数执行失败:`, error);
        }
      });
    }
  }

  // 销毁聊天管理器
  destroy() {
    this.leaveRoom();
    wsManager.close();
    this.messages.clear();
    this.unreadCounts.clear();
    this.listeners.clear();
    this.isInitialized = false;
  }
}

// 创建聊天管理器实例
const chatManager = new ChatManager();

export default chatManager;

5.5.3 聊天页面实现

<!-- pages/chat/chat.vue -->
<template>
  <view class="chat-page">
    <!-- 导航栏 -->
    <view class="chat-header">
      <view class="header-left">
        <button @click="goBack" class="back-btn">返回</button>
      </view>
      <view class="header-title">{{ roomInfo.name }}</view>
      <view class="header-right">
        <text class="online-status" :class="{ online: isOnline }">{{ isOnline ? '在线' : '离线' }}</text>
      </view>
    </view>
    
    <!-- 消息列表 -->
    <scroll-view 
      class="message-list" 
      scroll-y 
      :scroll-top="scrollTop"
      @scrolltoupper="loadMoreMessages"
    >
      <view class="message-item" 
        v-for="message in messages" 
        :key="message.id"
        :class="{ 'own-message': message.senderId === currentUserId }"
      >
        <!-- 时间分隔线 -->
        <view class="time-divider" v-if="shouldShowTime(message)">
          <text>{{ formatTime(message.timestamp) }}</text>
        </view>
        
        <!-- 消息内容 -->
        <view class="message-content">
          <image v-if="!isOwnMessage(message)" :src="message.senderAvatar" class="avatar" />
          
          <view class="message-bubble" :class="{ 'own-bubble': isOwnMessage(message) }">
            <!-- 文本消息 -->
            <text v-if="message.type === 'text'" class="message-text">{{ message.content }}</text>
            
            <!-- 图片消息 -->
            <image v-else-if="message.type === 'image'" 
              :src="message.content" 
              class="message-image"
              @click="previewImage(message.content)"
            />
            
            <!-- 消息状态 -->
            <view class="message-status" v-if="isOwnMessage(message)">
              <text v-if="message.status === 'sending'">发送中</text>
              <text v-else-if="message.status === 'sent'">已发送</text>
              <text v-else-if="message.status === 'delivered'">已送达</text>
              <text v-else-if="message.status === 'read'">已读</text>
              <text v-else-if="message.status === 'failed'" class="error">发送失败</text>
            </view>
          </view>
          
          <image v-if="isOwnMessage(message)" :src="currentUserAvatar" class="avatar" />
        </view>
      </view>
    </scroll-view>
    
    <!-- 输入区域 -->
    <view class="input-area">
      <view class="input-row">
        <button @click="selectImage" class="action-btn">📷</button>
        <input 
          v-model="inputText" 
          class="message-input" 
          placeholder="输入消息..."
          @confirm="sendTextMessage"
          confirm-type="send"
        />
        <button @click="sendTextMessage" class="send-btn" :disabled="!inputText.trim()">发送</button>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue';
import chatManager from '@/utils/chat';
import { useUserStore } from '@/stores/user';

// 页面参数
const props = defineProps({
  roomId: String
});

// 状态管理
const userStore = useUserStore();

// 响应式数据
const messages = ref([]);
const inputText = ref('');
const scrollTop = ref(0);
const isOnline = ref(false);
const roomInfo = ref({ name: '聊天室' });

// 计算属性
const currentUserId = computed(() => userStore.userInfo?.id);
const currentUserAvatar = computed(() => userStore.avatar);

// 方法
const isOwnMessage = (message) => {
  return message.senderId === currentUserId.value;
};

const shouldShowTime = (message) => {
  // 简单的时间显示逻辑,可以根据需要优化
  return true;
};

const formatTime = (timestamp) => {
  const date = new Date(timestamp);
  const now = new Date();
  
  if (date.toDateString() === now.toDateString()) {
    return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
  } else {
    return date.toLocaleDateString('zh-CN') + ' ' + date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
  }
};

const sendTextMessage = () => {
  if (!inputText.value.trim()) return;
  
  chatManager.sendMessage(props.roomId, inputText.value.trim());
  inputText.value = '';
  
  // 滚动到底部
  nextTick(() => {
    scrollToBottom();
  });
};

const selectImage = () => {
  uni.chooseImage({
    count: 1,
    sizeType: ['compressed'],
    sourceType: ['album', 'camera'],
    success: (res) => {
      const filePath = res.tempFilePaths[0];
      chatManager.sendImageMessage(props.roomId, filePath);
    }
  });
};

const previewImage = (imageUrl) => {
  uni.previewImage({
    urls: [imageUrl]
  });
};

const loadMoreMessages = () => {
  // 加载更多历史消息
  console.log('加载更多消息');
};

const scrollToBottom = () => {
  scrollTop.value = 999999;
};

const goBack = () => {
  uni.navigateBack();
};

// 聊天事件监听
const setupChatListeners = () => {
  chatManager.on('new_message', ({ roomId, message }) => {
    if (roomId === props.roomId) {
      messages.value.push(message);
      nextTick(() => {
        scrollToBottom();
      });
    }
  });
  
  chatManager.on('message_sent', ({ roomId, message }) => {
    if (roomId === props.roomId) {
      const index = messages.value.findIndex(msg => msg.id === message.id);
      if (index === -1) {
        messages.value.push(message);
      }
    }
  });
  
  chatManager.on('message_status_updated', ({ roomId, messageId, status }) => {
    if (roomId === props.roomId) {
      const message = messages.value.find(msg => msg.id === messageId);
      if (message) {
        message.status = status;
      }
    }
  });
  
  chatManager.on('user_status_updated', (data) => {
    isOnline.value = data.online;
  });
};

// 生命周期
onMounted(async () => {
  try {
    // 初始化聊天管理器
    await chatManager.initialize();
    
    // 设置事件监听
    setupChatListeners();
    
    // 加入房间
    chatManager.joinRoom(props.roomId);
    
    // 加载历史消息
    messages.value = chatManager.getRoomMessages(props.roomId);
    
    // 滚动到底部
    nextTick(() => {
      scrollToBottom();
    });
    
  } catch (error) {
    console.error('初始化聊天失败:', error);
    uni.showToast({
      title: '连接失败',
      icon: 'error'
    });
  }
});

onUnmounted(() => {
  chatManager.leaveRoom();
});
</script>

<style>
.chat-page {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: #f5f5f5;
}

.chat-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 15px;
  background-color: #fff;
  border-bottom: 1px solid #e0e0e0;
}

.back-btn {
  background: none;
  border: none;
  color: #007AFF;
  font-size: 16px;
}

.header-title {
  font-size: 18px;
  font-weight: bold;
}

.online-status {
  font-size: 12px;
  color: #999;
}

.online-status.online {
  color: #4CAF50;
}

.message-list {
  flex: 1;
  padding: 10px;
}

.message-item {
  margin-bottom: 15px;
}

.time-divider {
  text-align: center;
  margin: 10px 0;
}

.time-divider text {
  background-color: rgba(0, 0, 0, 0.1);
  color: #666;
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 10px;
}

.message-content {
  display: flex;
  align-items: flex-end;
}

.own-message .message-content {
  flex-direction: row-reverse;
}

.avatar {
  width: 40px;
  height: 40px;
  border-radius: 20px;
  margin: 0 8px;
}

.message-bubble {
  max-width: 70%;
  padding: 10px 15px;
  border-radius: 18px;
  background-color: #fff;
  position: relative;
}

.own-bubble {
  background-color: #007AFF;
  color: #fff;
}

.message-text {
  font-size: 16px;
  line-height: 1.4;
}

.message-image {
  max-width: 200px;
  max-height: 200px;
  border-radius: 8px;
}

.message-status {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.7);
  margin-top: 5px;
  text-align: right;
}

.message-status .error {
  color: #FF3B30;
}

.input-area {
  background-color: #fff;
  border-top: 1px solid #e0e0e0;
  padding: 10px;
}

.input-row {
  display: flex;
  align-items: center;
}

.action-btn {
  background: none;
  border: none;
  font-size: 24px;
  margin-right: 10px;
}

.message-input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid #e0e0e0;
  border-radius: 20px;
  font-size: 16px;
  margin-right: 10px;
}

.send-btn {
  background-color: #007AFF;
  color: #fff;
  border: none;
  padding: 8px 16px;
  border-radius: 20px;
  font-size: 16px;
}

.send-btn:disabled {
  background-color: #ccc;
}
</style>

5.6 本章总结

在本章中,我们深入学习了 UniApp 中的网络请求与数据处理,主要包括以下内容:

学习要点回顾

  1. 网络请求基础

    • uni.request() API 的使用方法
    • Promise 封装和错误处理
    • 请求拦截器和响应拦截器
    • 文件上传和下载功能
  2. 数据缓存策略

    • UniApp 本地存储 API
    • 缓存管理和过期机制
    • 数据加密和安全存储
    • 缓存装饰器的实现
  3. 状态管理

    • Vuex 在 UniApp 中的应用
    • Pinia 状态管理库的使用
    • 数据持久化方案
    • 状态同步和管理
  4. 接口封装

    • 统一的 API 管理
    • 请求和响应标准化
    • 错误处理和重试机制
    • 批量请求和分页处理
  5. 实时通信

    • WebSocket 连接管理
    • 聊天系统实现
    • 消息队列和状态管理
    • 断线重连和心跳机制

实践练习

  1. 创建网络请求工具类

    • 封装 uni.request() 为通用的 HTTP 客户端
    • 实现请求拦截器,自动添加 token 和公共参数
    • 实现响应拦截器,统一处理错误和数据格式
    • 添加请求重试和超时处理机制
  2. 实现数据缓存系统

    • 创建缓存管理器,支持过期时间设置
    • 实现 LRU 缓存策略,自动清理过期数据
    • 添加数据加密功能,保护敏感信息
    • 实现缓存装饰器,简化缓存使用
  3. 构建状态管理方案

    • 使用 Pinia 创建用户、购物车、应用状态模块
    • 实现状态持久化,支持应用重启后恢复状态
    • 添加状态同步功能,支持多端数据一致性
    • 实现权限控制和角色管理

常见问题解答

Q: 如何处理网络请求的并发控制? A: 可以使用 Promise.all() 进行并发请求,使用 Promise.allSettled() 处理部分失败的情况,或者实现请求队列来控制并发数量。

Q: 如何优化大量数据的缓存性能? A: 使用分页缓存、虚拟滚动、数据压缩、索引优化等技术,避免一次性加载过多数据。

Q: WebSocket 连接断开后如何处理? A: 实现自动重连机制,使用指数退避算法控制重连间隔,保存未发送的消息到队列中,连接恢复后重新发送。

Q: 如何保证数据的安全性? A: 使用 HTTPS 协议、数据加密存储、token 过期机制、请求签名验证等安全措施。

Q: 如何处理不同平台的兼容性问题? A: 使用条件编译、平台检测、功能降级等方式,确保在不同平台上的兼容性。

最佳实践建议

  1. 网络请求优化

    • 合理设置超时时间和重试次数
    • 使用请求去重避免重复请求
    • 实现请求缓存减少网络开销
    • 使用 loading 状态提升用户体验
  2. 数据管理

    • 建立清晰的数据流向和状态管理
    • 合理使用缓存策略提升性能
    • 定期清理过期和无用数据
    • 实现数据备份和恢复机制
  3. 错误处理

    • 建立完善的错误处理机制
    • 提供友好的错误提示信息
    • 实现错误上报和监控
    • 添加降级和容错处理
  4. 性能优化

    • 使用防抖和节流控制请求频率
    • 实现数据预加载和懒加载
    • 优化数据结构和算法
    • 监控和分析性能指标
  5. 安全考虑

    • 验证和过滤用户输入
    • 使用安全的数据传输和存储
    • 实现访问控制和权限验证
    • 定期更新安全策略

通过本章的学习,你应该能够熟练地在 UniApp 中处理网络请求、管理应用状态、实现数据缓存和实时通信功能。下一章我们将学习《组件开发与复用》,包括自定义组件、组件通信、组件库开发等内容。

5.1.2 请求拦截器和响应拦截器

拦截器可以在请求发送前和响应返回后进行统一处理,如添加 token、错误处理、loading 状态等。

// utils/interceptor.js
class RequestInterceptor {
  constructor() {
    this.requestInterceptors = [];
    this.responseInterceptors = [];
    this.errorInterceptors = [];
  }

  // 添加请求拦截器
  addRequestInterceptor(interceptor) {
    this.requestInterceptors.push(interceptor);
  }

  // 添加响应拦截器
  addResponseInterceptor(interceptor) {
    this.responseInterceptors.push(interceptor);
  }

  // 添加错误拦截器
  addErrorInterceptor(interceptor) {
    this.errorInterceptors.push(interceptor);
  }

  // 执行请求拦截器
  async executeRequestInterceptors(config) {
    let result = config;
    for (const interceptor of this.requestInterceptors) {
      result = await interceptor(result);
    }
    return result;
  }

  // 执行响应拦截器
  async executeResponseInterceptors(response) {
    let result = response;
    for (const interceptor of this.responseInterceptors) {
      result = await interceptor(result);
    }
    return result;
  }

  // 执行错误拦截器
  async executeErrorInterceptors(error) {
    let result = error;
    for (const interceptor of this.errorInterceptors) {
      result = await interceptor(result);
    }
    return result;
  }
}

// 创建拦截器实例
const interceptor = new RequestInterceptor();

// 请求拦截器 - 添加 token
interceptor.addRequestInterceptor((config) => {
  const token = uni.getStorageSync('token');
  if (token) {
    config.header = config.header || {};
    config.header.Authorization = `Bearer ${token}`;
  }
  
  // 显示 loading
  if (config.showLoading !== false) {
    uni.showLoading({
      title: config.loadingText || '请求中...',
      mask: true
    });
  }
  
  console.log('请求发送:', config);
  return config;
});

// 响应拦截器 - 统一处理响应
interceptor.addResponseInterceptor((response) => {
  // 隐藏 loading
  uni.hideLoading();
  
  console.log('响应接收:', response);
  
  // 统一处理业务错误码
  if (response.data && response.data.code !== 200) {
    const error = new Error(response.data.message || '业务处理失败');
    error.code = response.data.code;
    throw error;
  }
  
  return response;
});

// 错误拦截器 - 统一错误处理
interceptor.addErrorInterceptor((error) => {
  // 隐藏 loading
  uni.hideLoading();
  
  console.error('请求错误:', error);
  
  // 根据错误类型进行处理
  if (error.code === 401) {
    // token 过期,跳转到登录页
    uni.removeStorageSync('token');
    uni.removeStorageSync('userInfo');
    uni.reLaunch({
      url: '/pages/login/login'
    });
  } else if (error.code === 403) {
    uni.showToast({
      title: '权限不足',
      icon: 'error'
    });
  } else if (error.code === 500) {
    uni.showToast({
      title: '服务器错误',
      icon: 'error'
    });
  } else {
    uni.showToast({
      title: error.message || '请求失败',
      icon: 'error'
    });
  }
  
  throw error;
});

export default interceptor;

集成拦截器的请求类

// utils/http.js
import interceptor from './interceptor.js';

class Http {
  constructor(baseURL = '') {
    this.baseURL = baseURL;
    this.timeout = 10000;
  }

  async request(config) {
    try {
      // 执行请求拦截器
      const processedConfig = await interceptor.executeRequestInterceptors({
        ...config,
        url: this.baseURL + config.url,
        timeout: config.timeout || this.timeout
      });

      // 发送请求
      const response = await this._sendRequest(processedConfig);

      // 执行响应拦截器
      return await interceptor.executeResponseInterceptors(response);
      
    } catch (error) {
      // 执行错误拦截器
      return await interceptor.executeErrorInterceptors(error);
    }
  }

  _sendRequest(config) {
    return new Promise((resolve, reject) => {
      uni.request({
        ...config,
        success: resolve,
        fail: reject
      });
    });
  }

  get(url, params = {}, config = {}) {
    const queryString = Object.keys(params)
      .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
      .join('&');
    
    if (queryString) {
      url += (url.includes('?') ? '&' : '?') + queryString;
    }

    return this.request({ ...config, url, method: 'GET' });
  }

  post(url, data = {}, config = {}) {
    return this.request({ ...config, url, method: 'POST', data });
  }

  put(url, data = {}, config = {}) {
    return this.request({ ...config, url, method: 'PUT', data });
  }

  delete(url, config = {}) {
    return this.request({ ...config, url, method: 'DELETE' });
  }
}

const http = new Http('https://api.example.com');
export default http;

5.1.3 错误处理和重试机制

// utils/retry.js
class RetryRequest {
  constructor(http) {
    this.http = http;
    this.defaultRetryConfig = {
      retries: 3,
      retryDelay: 1000,
      retryCondition: (error) => {
        // 网络错误或 5xx 错误才重试
        return !error.response || (error.response.status >= 500);
      }
    };
  }

  async request(config) {
    const retryConfig = {
      ...this.defaultRetryConfig,
      ...config.retry
    };

    let lastError;
    
    for (let attempt = 0; attempt <= retryConfig.retries; attempt++) {
      try {
        return await this.http.request(config);
      } catch (error) {
        lastError = error;
        
        // 最后一次尝试或不满足重试条件
        if (attempt === retryConfig.retries || !retryConfig.retryCondition(error)) {
          throw error;
        }
        
        // 等待后重试
        await this.delay(retryConfig.retryDelay * Math.pow(2, attempt));
        console.log(`请求重试 ${attempt + 1}/${retryConfig.retries}`);
      }
    }
    
    throw lastError;
  }

  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// 使用示例
const retryHttp = new RetryRequest(http);

// 带重试的请求
retryHttp.request({
  url: '/api/data',
  method: 'GET',
  retry: {
    retries: 5,
    retryDelay: 2000,
    retryCondition: (error) => {
      return error.code === 'NETWORK_ERROR' || error.status >= 500;
    }
  }
}).then(response => {
  console.log('请求成功:', response);
}).catch(error => {
  console.error('请求最终失败:', error);
});

5.1.4 文件上传和下载

文件上传

// utils/upload.js
class FileUploader {
  constructor(baseURL) {
    this.baseURL = baseURL;
  }

  // 选择并上传图片
  async uploadImage(options = {}) {
    try {
      // 选择图片
      const chooseResult = await this.chooseImage({
        count: options.count || 1,
        sizeType: options.sizeType || ['compressed'],
        sourceType: options.sourceType || ['album', 'camera']
      });

      const uploadPromises = chooseResult.tempFilePaths.map(filePath => 
        this.uploadFile(filePath, options)
      );

      return await Promise.all(uploadPromises);
      
    } catch (error) {
      console.error('上传失败:', error);
      throw error;
    }
  }

  // 选择图片
  chooseImage(options) {
    return new Promise((resolve, reject) => {
      uni.chooseImage({
        ...options,
        success: resolve,
        fail: reject
      });
    });
  }

  // 上传单个文件
  uploadFile(filePath, options = {}) {
    return new Promise((resolve, reject) => {
      const token = uni.getStorageSync('token');
      
      const uploadTask = uni.uploadFile({
        url: this.baseURL + (options.url || '/upload'),
        filePath,
        name: options.name || 'file',
        formData: options.formData || {},
        header: {
          Authorization: token ? `Bearer ${token}` : '',
          ...options.header
        },
        success: (res) => {
          try {
            const data = JSON.parse(res.data);
            if (data.code === 200) {
              resolve(data.data);
            } else {
              reject(new Error(data.message || '上传失败'));
            }
          } catch (e) {
            reject(new Error('响应解析失败'));
          }
        },
        fail: reject
      });

      // 监听上传进度
      if (options.onProgress) {
        uploadTask.onProgressUpdate(options.onProgress);
      }
    });
  }

  // 压缩图片
  async compressImage(src, quality = 0.8) {
    return new Promise((resolve, reject) => {
      uni.compressImage({
        src,
        quality,
        success: resolve,
        fail: reject
      });
    });
  }
}

const uploader = new FileUploader('https://api.example.com');
export default uploader;

文件下载

// utils/download.js
class FileDownloader {
  constructor(baseURL) {
    this.baseURL = baseURL;
  }

  // 下载文件
  async downloadFile(url, options = {}) {
    try {
      const downloadTask = uni.downloadFile({
        url: this.baseURL + url,
        header: options.header || {},
        success: (res) => {
          if (res.statusCode === 200) {
            return res.tempFilePath;
          } else {
            throw new Error('下载失败');
          }
        }
      });

      // 监听下载进度
      if (options.onProgress) {
        downloadTask.onProgressUpdate(options.onProgress);
      }

      return await this.promisify(downloadTask);
      
    } catch (error) {
      console.error('下载失败:', error);
      throw error;
    }
  }

  // 保存图片到相册
  async saveImageToPhotosAlbum(filePath) {
    try {
      // 检查权限
      const authResult = await this.getSetting();
      if (!authResult.authSetting['scope.writePhotosAlbum']) {
        await this.authorize('scope.writePhotosAlbum');
      }

      return await this.saveImage(filePath);
      
    } catch (error) {
      if (error.errMsg.includes('auth deny')) {
        uni.showModal({
          title: '提示',
          content: '需要相册权限才能保存图片,请在设置中开启',
          confirmText: '去设置',
          success: (res) => {
            if (res.confirm) {
              uni.openSetting();
            }
          }
        });
      }
      throw error;
    }
  }

  // 获取设置
  getSetting() {
    return new Promise((resolve, reject) => {
      uni.getSetting({
        success: resolve,
        fail: reject
      });
    });
  }

  // 请求授权
  authorize(scope) {
    return new Promise((resolve, reject) => {
      uni.authorize({
        scope,
        success: resolve,
        fail: reject
      });
    });
  }

  // 保存图片
  saveImage(filePath) {
    return new Promise((resolve, reject) => {
      uni.saveImageToPhotosAlbum({
        filePath,
        success: resolve,
        fail: reject
      });
    });
  }

  // Promise 化
  promisify(task) {
    return new Promise((resolve, reject) => {
      task.then ? task.then(resolve).catch(reject) : resolve(task);
    });
  }
}

const downloader = new FileDownloader('https://api.example.com');
export default downloader;

使用示例

// pages/upload/upload.vue
<template>
  <view class="upload-page">
    <view class="upload-area" @click="selectAndUpload">
      <image v-if="imageUrl" :src="imageUrl" class="preview-image" />
      <view v-else class="upload-placeholder">
        <text>点击上传图片</text>
      </view>
    </view>
    
    <progress v-if="uploading" :percent="uploadProgress" show-info />
    
    <button @click="downloadAndSave" :disabled="!imageUrl">下载并保存</button>
  </view>
</template>

<script>
import uploader from '@/utils/upload.js';
import downloader from '@/utils/download.js';

export default {
  data() {
    return {
      imageUrl: '',
      uploading: false,
      uploadProgress: 0
    };
  },
  
  methods: {
    // 选择并上传图片
    async selectAndUpload() {
      try {
        this.uploading = true;
        this.uploadProgress = 0;
        
        const results = await uploader.uploadImage({
          count: 1,
          onProgress: (progress) => {
            this.uploadProgress = progress.progress;
          }
        });
        
        this.imageUrl = results[0].url;
        
        uni.showToast({
          title: '上传成功',
          icon: 'success'
        });
        
      } catch (error) {
        console.error('上传失败:', error);
        uni.showToast({
          title: '上传失败',
          icon: 'error'
        });
      } finally {
        this.uploading = false;
      }
    },
    
    // 下载并保存图片
    async downloadAndSave() {
      try {
        uni.showLoading({ title: '下载中...' });
        
        const filePath = await downloader.downloadFile(this.imageUrl);
        await downloader.saveImageToPhotosAlbum(filePath);
        
        uni.showToast({
          title: '保存成功',
          icon: 'success'
        });
        
      } catch (error) {
        console.error('保存失败:', error);
        uni.showToast({
          title: '保存失败',
          icon: 'error'
        });
      } finally {
        uni.hideLoading();
      }
    }
  }
};
</script>

<style>
.upload-area {
  width: 200px;
  height: 200px;
  border: 2px dashed #ccc;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 20px auto;
}

.preview-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.upload-placeholder {
  color: #999;
}
</style>

5.2 数据缓存

5.2.1 本地存储 API

UniApp 提供了同步和异步两种本地存储方式,支持存储字符串、对象等数据类型。

同步存储 API

// 存储数据
uni.setStorageSync('key', 'value');
uni.setStorageSync('user', { name: '张三', age: 25 });

// 读取数据
const value = uni.getStorageSync('key');
const user = uni.getStorageSync('user');

// 删除数据
uni.removeStorageSync('key');

// 清空所有数据
uni.clearStorageSync();

// 获取存储信息
const info = uni.getStorageInfoSync();
console.log('存储的 key 列表:', info.keys);
console.log('当前占用空间:', info.currentSize);
console.log('限制空间:', info.limitSize);

异步存储 API

// 存储数据
uni.setStorage({
  key: 'user',
  data: { name: '张三', age: 25 },
  success: () => {
    console.log('存储成功');
  },
  fail: (error) => {
    console.error('存储失败:', error);
  }
});

// 读取数据
uni.getStorage({
  key: 'user',
  success: (res) => {
    console.log('读取成功:', res.data);
  },
  fail: (error) => {
    console.error('读取失败:', error);
  }
});

// Promise 封装
function setStorageAsync(key, data) {
  return new Promise((resolve, reject) => {
    uni.setStorage({
      key,
      data,
      success: resolve,
      fail: reject
    });
  });
}

function getStorageAsync(key) {
  return new Promise((resolve, reject) => {
    uni.getStorage({
      key,
      success: (res) => resolve(res.data),
      fail: reject
    });
  });
}

5.2.2 缓存策略和过期管理

// utils/cache.js
class CacheManager {
  constructor() {
    this.prefix = 'app_cache_';
    this.defaultExpire = 24 * 60 * 60 * 1000; // 24小时
  }

  // 生成缓存键
  _generateKey(key) {
    return this.prefix + key;
  }

  // 设置缓存
  set(key, data, expire = this.defaultExpire) {
    const cacheData = {
      data,
      timestamp: Date.now(),
      expire
    };
    
    try {
      uni.setStorageSync(this._generateKey(key), cacheData);
      return true;
    } catch (error) {
      console.error('缓存设置失败:', error);
      return false;
    }
  }

  // 获取缓存
  get(key) {
    try {
      const cacheData = uni.getStorageSync(this._generateKey(key));
      
      if (!cacheData) {
        return null;
      }

      // 检查是否过期
      if (this._isExpired(cacheData)) {
        this.remove(key);
        return null;
      }

      return cacheData.data;
    } catch (error) {
      console.error('缓存读取失败:', error);
      return null;
    }
  }

  // 检查是否过期
  _isExpired(cacheData) {
    if (!cacheData.expire) {
      return false; // 永不过期
    }
    
    return Date.now() - cacheData.timestamp > cacheData.expire;
  }

  // 删除缓存
  remove(key) {
    try {
      uni.removeStorageSync(this._generateKey(key));
      return true;
    } catch (error) {
      console.error('缓存删除失败:', error);
      return false;
    }
  }

  // 清空所有缓存
  clear() {
    try {
      const info = uni.getStorageInfoSync();
      const keysToRemove = info.keys.filter(key => key.startsWith(this.prefix));
      
      keysToRemove.forEach(key => {
        uni.removeStorageSync(key);
      });
      
      return true;
    } catch (error) {
      console.error('缓存清空失败:', error);
      return false;
    }
  }

  // 清理过期缓存
  clearExpired() {
    try {
      const info = uni.getStorageInfoSync();
      const cacheKeys = info.keys.filter(key => key.startsWith(this.prefix));
      
      cacheKeys.forEach(key => {
        const cacheData = uni.getStorageSync(key);
        if (cacheData && this._isExpired(cacheData)) {
          uni.removeStorageSync(key);
        }
      });
      
      return true;
    } catch (error) {
      console.error('过期缓存清理失败:', error);
      return false;
    }
  }

  // 获取缓存大小
  getSize() {
    try {
      const info = uni.getStorageInfoSync();
      return {
        currentSize: info.currentSize,
        limitSize: info.limitSize,
        keys: info.keys.filter(key => key.startsWith(this.prefix))
      };
    } catch (error) {
      console.error('获取缓存大小失败:', error);
      return null;
    }
  }

  // 缓存装饰器
  cached(key, expire) {
    return (target, propertyName, descriptor) => {
      const originalMethod = descriptor.value;
      
      descriptor.value = async function(...args) {
        const cacheKey = `${key}_${JSON.stringify(args)}`;
        
        // 尝试从缓存获取
        let result = this.get(cacheKey);
        if (result !== null) {
          return result;
        }
        
        // 执行原方法
        result = await originalMethod.apply(this, args);
        
        // 缓存结果
        this.set(cacheKey, result, expire);
        
        return result;
      };
      
      return descriptor;
    };
  }
}

// 创建缓存管理器实例
const cache = new CacheManager();

// 使用示例
class ApiService {
  constructor() {
    this.cache = cache;
  }

  // 缓存用户信息 1小时
  async getUserInfo(userId) {
    const cacheKey = `user_${userId}`;
    
    // 先尝试从缓存获取
    let userInfo = this.cache.get(cacheKey);
    if (userInfo) {
      console.log('从缓存获取用户信息');
      return userInfo;
    }
    
    // 从服务器获取
    console.log('从服务器获取用户信息');
    userInfo = await http.get(`/users/${userId}`);
    
    // 缓存 1小时
    this.cache.set(cacheKey, userInfo, 60 * 60 * 1000);
    
    return userInfo;
  }

  // 缓存商品列表 30分钟
  async getProductList(category, page = 1) {
    const cacheKey = `products_${category}_${page}`;
    
    let products = this.cache.get(cacheKey);
    if (products) {
      return products;
    }
    
    products = await http.get('/products', { category, page });
    this.cache.set(cacheKey, products, 30 * 60 * 1000);
    
    return products;
  }

  // 清除用户相关缓存
  clearUserCache(userId) {
    this.cache.remove(`user_${userId}`);
  }
}

export default cache;
export { CacheManager, ApiService };

5.2.3 数据加密和安全存储

// utils/secure-storage.js
import CryptoJS from 'crypto-js';

class SecureStorage {
  constructor(secretKey = 'your-secret-key') {
    this.secretKey = secretKey;
  }

  // 加密数据
  _encrypt(data) {
    try {
      const jsonString = JSON.stringify(data);
      const encrypted = CryptoJS.AES.encrypt(jsonString, this.secretKey).toString();
      return encrypted;
    } catch (error) {
      console.error('加密失败:', error);
      throw error;
    }
  }

  // 解密数据
  _decrypt(encryptedData) {
    try {
      const decrypted = CryptoJS.AES.decrypt(encryptedData, this.secretKey);
      const jsonString = decrypted.toString(CryptoJS.enc.Utf8);
      return JSON.parse(jsonString);
    } catch (error) {
      console.error('解密失败:', error);
      throw error;
    }
  }

  // 安全存储
  setSecure(key, data) {
    try {
      const encryptedData = this._encrypt(data);
      uni.setStorageSync(key, encryptedData);
      return true;
    } catch (error) {
      console.error('安全存储失败:', error);
      return false;
    }
  }

  // 安全读取
  getSecure(key) {
    try {
      const encryptedData = uni.getStorageSync(key);
      if (!encryptedData) {
        return null;
      }
      
      return this._decrypt(encryptedData);
    } catch (error) {
      console.error('安全读取失败:', error);
      return null;
    }
  }

  // 删除安全数据
  removeSecure(key) {
    try {
      uni.removeStorageSync(key);
      return true;
    } catch (error) {
      console.error('删除安全数据失败:', error);
      return false;
    }
  }

  // 存储敏感信息(如token、密码等)
  setToken(token) {
    return this.setSecure('secure_token', token);
  }

  getToken() {
    return this.getSecure('secure_token');
  }

  removeToken() {
    return this.removeSecure('secure_token');
  }

  // 存储用户凭证
  setCredentials(credentials) {
    return this.setSecure('user_credentials', credentials);
  }

  getCredentials() {
    return this.getSecure('user_credentials');
  }

  removeCredentials() {
    return this.removeSecure('user_credentials');
  }
}

// 创建安全存储实例
const secureStorage = new SecureStorage('your-app-secret-key');

// 使用示例
class AuthManager {
  constructor() {
    this.storage = secureStorage;
  }

  // 登录
  async login(username, password, rememberMe = false) {
    try {
      const response = await http.post('/auth/login', {
        username,
        password
      });

      const { token, userInfo } = response.data;

      // 安全存储 token
      this.storage.setToken(token);
      
      // 存储用户信息
      uni.setStorageSync('userInfo', userInfo);

      // 如果选择记住密码,安全存储凭证
      if (rememberMe) {
        this.storage.setCredentials({ username, password });
      }

      return { token, userInfo };
    } catch (error) {
      console.error('登录失败:', error);
      throw error;
    }
  }

  // 登出
  logout() {
    this.storage.removeToken();
    uni.removeStorageSync('userInfo');
    // 注意:不删除记住的凭证,除非用户主动选择
  }

  // 获取当前token
  getToken() {
    return this.storage.getToken();
  }

  // 检查登录状态
  isLoggedIn() {
    return !!this.getToken();
  }

  // 获取记住的凭证
  getRememberedCredentials() {
    return this.storage.getCredentials();
  }

  // 清除记住的凭证
  clearRememberedCredentials() {
    this.storage.removeCredentials();
  }
}

export default secureStorage;
export { SecureStorage, AuthManager };

5.3 状态管理

5.3.1 Vuex 在 UniApp 中的使用

安装和配置 Vuex

npm install vuex@next
// store/index.js
import { createStore } from 'vuex';
import user from './modules/user';
import cart from './modules/cart';
import app from './modules/app';

const store = createStore({
  modules: {
    user,
    cart,
    app
  },
  
  // 全局状态
  state: {
    loading: false,
    networkStatus: 'online'
  },
  
  // 全局 getters
  getters: {
    isLoading: state => state.loading,
    isOnline: state => state.networkStatus === 'online'
  },
  
  // 全局 mutations
  mutations: {
    SET_LOADING(state, loading) {
      state.loading = loading;
    },
    
    SET_NETWORK_STATUS(state, status) {
      state.networkStatus = status;
    }
  },
  
  // 全局 actions
  actions: {
    setLoading({ commit }, loading) {
      commit('SET_LOADING', loading);
    },
    
    setNetworkStatus({ commit }, status) {
      commit('SET_NETWORK_STATUS', status);
    }
  }
});

export default store;

用户模块

// store/modules/user.js
import { AuthManager } from '@/utils/secure-storage';
import http from '@/utils/http';

const authManager = new AuthManager();

const state = {
  userInfo: null,
  token: null,
  isLoggedIn: false,
  permissions: [],
  roles: []
};

const getters = {
  userInfo: state => state.userInfo,
  token: state => state.token,
  isLoggedIn: state => state.isLoggedIn,
  permissions: state => state.permissions,
  roles: state => state.roles,
  
  // 检查权限
  hasPermission: state => permission => {
    return state.permissions.includes(permission);
  },
  
  // 检查角色
  hasRole: state => role => {
    return state.roles.includes(role);
  }
};

const mutations = {
  SET_USER_INFO(state, userInfo) {
    state.userInfo = userInfo;
  },
  
  SET_TOKEN(state, token) {
    state.token = token;
  },
  
  SET_LOGIN_STATUS(state, status) {
    state.isLoggedIn = status;
  },
  
  SET_PERMISSIONS(state, permissions) {
    state.permissions = permissions;
  },
  
  SET_ROLES(state, roles) {
    state.roles = roles;
  },
  
  CLEAR_USER_DATA(state) {
    state.userInfo = null;
    state.token = null;
    state.isLoggedIn = false;
    state.permissions = [];
    state.roles = [];
  }
};

const actions = {
  // 登录
  async login({ commit, dispatch }, { username, password, rememberMe }) {
    try {
      dispatch('setLoading', true, { root: true });
      
      const result = await authManager.login(username, password, rememberMe);
      
      commit('SET_TOKEN', result.token);
      commit('SET_USER_INFO', result.userInfo);
      commit('SET_LOGIN_STATUS', true);
      
      // 获取用户权限
      await dispatch('getUserPermissions');
      
      return result;
    } catch (error) {
      throw error;
    } finally {
      dispatch('setLoading', false, { root: true });
    }
  },
  
  // 登出
  async logout({ commit }) {
    try {
      // 调用登出接口
      await http.post('/auth/logout');
    } catch (error) {
      console.error('登出接口调用失败:', error);
    } finally {
      // 清除本地数据
      authManager.logout();
      commit('CLEAR_USER_DATA');
    }
  },
  
  // 获取用户信息
  async getUserInfo({ commit, state }) {
    try {
      if (!state.token) {
        throw new Error('未登录');
      }
      
      const response = await http.get('/user/info');
      const userInfo = response.data;
      
      commit('SET_USER_INFO', userInfo);
      uni.setStorageSync('userInfo', userInfo);
      
      return userInfo;
    } catch (error) {
      console.error('获取用户信息失败:', error);
      throw error;
    }
  },
  
  // 获取用户权限
  async getUserPermissions({ commit, state }) {
    try {
      if (!state.token) {
        return;
      }
      
      const response = await http.get('/user/permissions');
      const { permissions, roles } = response.data;
      
      commit('SET_PERMISSIONS', permissions);
      commit('SET_ROLES', roles);
      
    } catch (error) {
      console.error('获取用户权限失败:', error);
    }
  },
  
  // 初始化用户状态
  initUserState({ commit, dispatch }) {
    const token = authManager.getToken();
    const userInfo = uni.getStorageSync('userInfo');
    
    if (token && userInfo) {
      commit('SET_TOKEN', token);
      commit('SET_USER_INFO', userInfo);
      commit('SET_LOGIN_STATUS', true);
      
      // 获取最新权限
      dispatch('getUserPermissions');
    }
  },
  
  // 更新用户信息
  async updateUserInfo({ commit }, userInfo) {
    try {
      const response = await http.put('/user/info', userInfo);
      const updatedUserInfo = response.data;
      
      commit('SET_USER_INFO', updatedUserInfo);
      uni.setStorageSync('userInfo', updatedUserInfo);
      
      return updatedUserInfo;
    } catch (error) {
      console.error('更新用户信息失败:', error);
      throw error;
    }
  }
};

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
};

购物车模块

// store/modules/cart.js
const state = {
  items: [],
  totalCount: 0,
  totalPrice: 0
};

const getters = {
  items: state => state.items,
  totalCount: state => state.totalCount,
  totalPrice: state => state.totalPrice,
  
  // 获取商品数量
  getItemCount: state => productId => {
    const item = state.items.find(item => item.productId === productId);
    return item ? item.quantity : 0;
  },
  
  // 检查商品是否在购物车中
  isInCart: state => productId => {
    return state.items.some(item => item.productId === productId);
  }
};

const mutations = {
  SET_CART_ITEMS(state, items) {
    state.items = items;
    updateCartTotals(state);
  },
  
  ADD_TO_CART(state, { product, quantity = 1 }) {
    const existingItem = state.items.find(item => item.productId === product.id);
    
    if (existingItem) {
      existingItem.quantity += quantity;
    } else {
      state.items.push({
        productId: product.id,
        productName: product.name,
        productImage: product.image,
        price: product.price,
        quantity
      });
    }
    
    updateCartTotals(state);
  },
  
  REMOVE_FROM_CART(state, productId) {
    const index = state.items.findIndex(item => item.productId === productId);
    if (index > -1) {
      state.items.splice(index, 1);
    }
    updateCartTotals(state);
  },
  
  UPDATE_QUANTITY(state, { productId, quantity }) {
    const item = state.items.find(item => item.productId === productId);
    if (item) {
      if (quantity <= 0) {
        const index = state.items.indexOf(item);
        state.items.splice(index, 1);
      } else {
        item.quantity = quantity;
      }
    }
    updateCartTotals(state);
  },
  
  CLEAR_CART(state) {
    state.items = [];
    state.totalCount = 0;
    state.totalPrice = 0;
  }
};

const actions = {
  // 添加到购物车
  addToCart({ commit, dispatch }, { product, quantity = 1 }) {
    commit('ADD_TO_CART', { product, quantity });
    dispatch('saveCartToStorage');
    
    uni.showToast({
      title: '已添加到购物车',
      icon: 'success'
    });
  },
  
  // 从购物车移除
  removeFromCart({ commit, dispatch }, productId) {
    commit('REMOVE_FROM_CART', productId);
    dispatch('saveCartToStorage');
  },
  
  // 更新数量
  updateQuantity({ commit, dispatch }, { productId, quantity }) {
    commit('UPDATE_QUANTITY', { productId, quantity });
    dispatch('saveCartToStorage');
  },
  
  // 清空购物车
  clearCart({ commit, dispatch }) {
    commit('CLEAR_CART');
    dispatch('saveCartToStorage');
  },
  
  // 保存购物车到本地存储
  saveCartToStorage({ state }) {
    uni.setStorageSync('cart', state.items);
  },
  
  // 从本地存储加载购物车
  loadCartFromStorage({ commit }) {
    const cartItems = uni.getStorageSync('cart') || [];
    commit('SET_CART_ITEMS', cartItems);
  },
  
  // 同步购物车到服务器
  async syncCartToServer({ state, rootGetters }) {
    try {
      if (!rootGetters['user/isLoggedIn']) {
        return;
      }
      
      await http.post('/cart/sync', {
        items: state.items
      });
      
    } catch (error) {
      console.error('同步购物车失败:', error);
    }
  },
  
  // 从服务器加载购物车
  async loadCartFromServer({ commit, rootGetters }) {
    try {
      if (!rootGetters['user/isLoggedIn']) {
        return;
      }
      
      const response = await http.get('/cart');
      const cartItems = response.data.items || [];
      
      commit('SET_CART_ITEMS', cartItems);
      
    } catch (error) {
      console.error('加载购物车失败:', error);
    }
  }
};

// 辅助函数:更新购物车总计
function updateCartTotals(state) {
  state.totalCount = state.items.reduce((total, item) => total + item.quantity, 0);
  state.totalPrice = state.items.reduce((total, item) => total + (item.price * item.quantity), 0);
}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
};

应用模块

// store/modules/app.js
const state = {
  theme: 'light',
  language: 'zh-CN',
  systemInfo: null,
  networkType: 'unknown',
  tabBarBadge: {
    cart: 0,
    message: 0
  }
};

const getters = {
  theme: state => state.theme,
  language: state => state.language,
  systemInfo: state => state.systemInfo,
  networkType: state => state.networkType,
  tabBarBadge: state => state.tabBarBadge,
  
  isDarkMode: state => state.theme === 'dark',
  isIOS: state => state.systemInfo && state.systemInfo.platform === 'ios'
};

const mutations = {
  SET_THEME(state, theme) {
    state.theme = theme;
  },
  
  SET_LANGUAGE(state, language) {
    state.language = language;
  },
  
  SET_SYSTEM_INFO(state, systemInfo) {
    state.systemInfo = systemInfo;
  },
  
  SET_NETWORK_TYPE(state, networkType) {
    state.networkType = networkType;
  },
  
  SET_TAB_BAR_BADGE(state, { tab, count }) {
    state.tabBarBadge[tab] = count;
  },
  
  CLEAR_TAB_BAR_BADGE(state, tab) {
    state.tabBarBadge[tab] = 0;
  }
};

const actions = {
  // 切换主题
  toggleTheme({ commit, state }) {
    const newTheme = state.theme === 'light' ? 'dark' : 'light';
    commit('SET_THEME', newTheme);
    uni.setStorageSync('theme', newTheme);
  },
  
  // 设置语言
  setLanguage({ commit }, language) {
    commit('SET_LANGUAGE', language);
    uni.setStorageSync('language', language);
  },
  
  // 获取系统信息
  getSystemInfo({ commit }) {
    uni.getSystemInfo({
      success: (res) => {
        commit('SET_SYSTEM_INFO', res);
      }
    });
  },
  
  // 获取网络类型
  getNetworkType({ commit }) {
    uni.getNetworkType({
      success: (res) => {
        commit('SET_NETWORK_TYPE', res.networkType);
      }
    });
  },
  
  // 监听网络状态变化
  watchNetworkStatus({ commit }) {
    uni.onNetworkStatusChange((res) => {
      commit('SET_NETWORK_TYPE', res.networkType);
    });
  },
  
  // 设置 TabBar 角标
  setTabBarBadge({ commit }, { tab, count }) {
    commit('SET_TAB_BAR_BADGE', { tab, count });
    
    // 更新原生 TabBar 角标
    if (count > 0) {
      uni.setTabBarBadge({
        index: getTabIndex(tab),
        text: count.toString()
      });
    } else {
      uni.removeTabBarBadge({
        index: getTabIndex(tab)
      });
    }
  },
  
  // 清除 TabBar 角标
  clearTabBarBadge({ commit }, tab) {
    commit('CLEAR_TAB_BAR_BADGE', tab);
    uni.removeTabBarBadge({
      index: getTabIndex(tab)
    });
  },
  
  // 初始化应用状态
  initAppState({ commit, dispatch }) {
    // 加载主题
    const theme = uni.getStorageSync('theme') || 'light';
    commit('SET_THEME', theme);
    
    // 加载语言
    const language = uni.getStorageSync('language') || 'zh-CN';
    commit('SET_LANGUAGE', language);
    
    // 获取系统信息
    dispatch('getSystemInfo');
    
    // 获取网络状态
    dispatch('getNetworkType');
    
    // 监听网络状态变化
    dispatch('watchNetworkStatus');
  }
};

// 辅助函数:获取 Tab 索引
function getTabIndex(tab) {
  const tabMap = {
    home: 0,
    category: 1,
    cart: 2,
    profile: 3
  };
  return tabMap[tab] || 0;
}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
};

在 main.js 中注册 Vuex

// main.js
import { createSSRApp } from 'vue';
import App from './App.vue';
import store from './store';

export function createApp() {
  const app = createSSRApp(App);
  
  app.use(store);
  
  return {
    app
  };
}

在组件中使用 Vuex

// pages/index/index.vue
<template>
  <view class="home-page">
    <view class="user-info" v-if="isLoggedIn">
      <text>欢迎,{{ userInfo.name }}</text>
      <button @click="logout">退出登录</button>
    </view>
    
    <view class="cart-info">
      <text>购物车商品数量:{{ cartTotalCount }}</text>
      <button @click="addToCart">添加商品</button>
    </view>
    
    <button @click="toggleTheme">切换主题</button>
  </view>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'vuex';

export default {
  computed: {
    // 映射 state
    ...mapState('user', ['userInfo']),
    ...mapState('cart', ['totalCount']),
    
    // 映射 getters
    ...mapGetters('user', ['isLoggedIn']),
    ...mapGetters('cart', ['totalCount as cartTotalCount']),
    ...mapGetters('app', ['isDarkMode'])
  },
  
  methods: {
    // 映射 actions
    ...mapActions('user', ['logout']),
    ...mapActions('cart', ['addToCart']),
    ...mapActions('app', ['toggleTheme']),
    
    // 添加商品到购物车
    addToCart() {
      const product = {
        id: 1,
        name: '测试商品',
        price: 99.99,
        image: '/static/product.jpg'
      };
      
      this['cart/addToCart']({ product, quantity: 1 });
    }
  },
  
  onLoad() {
    // 初始化状态
    this.$store.dispatch('user/initUserState');
    this.$store.dispatch('cart/loadCartFromStorage');
    this.$store.dispatch('app/initAppState');
  }
};
</script>

”`