在现代移动应用开发中,网络请求和数据处理是核心功能之一。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 中的网络请求与数据处理,主要包括以下内容:
学习要点回顾
网络请求基础
uni.request()
API 的使用方法- Promise 封装和错误处理
- 请求拦截器和响应拦截器
- 文件上传和下载功能
数据缓存策略
- UniApp 本地存储 API
- 缓存管理和过期机制
- 数据加密和安全存储
- 缓存装饰器的实现
状态管理
- Vuex 在 UniApp 中的应用
- Pinia 状态管理库的使用
- 数据持久化方案
- 状态同步和管理
接口封装
- 统一的 API 管理
- 请求和响应标准化
- 错误处理和重试机制
- 批量请求和分页处理
实时通信
- WebSocket 连接管理
- 聊天系统实现
- 消息队列和状态管理
- 断线重连和心跳机制
实践练习
创建网络请求工具类
- 封装
uni.request()
为通用的 HTTP 客户端 - 实现请求拦截器,自动添加 token 和公共参数
- 实现响应拦截器,统一处理错误和数据格式
- 添加请求重试和超时处理机制
- 封装
实现数据缓存系统
- 创建缓存管理器,支持过期时间设置
- 实现 LRU 缓存策略,自动清理过期数据
- 添加数据加密功能,保护敏感信息
- 实现缓存装饰器,简化缓存使用
构建状态管理方案
- 使用 Pinia 创建用户、购物车、应用状态模块
- 实现状态持久化,支持应用重启后恢复状态
- 添加状态同步功能,支持多端数据一致性
- 实现权限控制和角色管理
常见问题解答
Q: 如何处理网络请求的并发控制? A: 可以使用 Promise.all() 进行并发请求,使用 Promise.allSettled() 处理部分失败的情况,或者实现请求队列来控制并发数量。
Q: 如何优化大量数据的缓存性能? A: 使用分页缓存、虚拟滚动、数据压缩、索引优化等技术,避免一次性加载过多数据。
Q: WebSocket 连接断开后如何处理? A: 实现自动重连机制,使用指数退避算法控制重连间隔,保存未发送的消息到队列中,连接恢复后重新发送。
Q: 如何保证数据的安全性? A: 使用 HTTPS 协议、数据加密存储、token 过期机制、请求签名验证等安全措施。
Q: 如何处理不同平台的兼容性问题? A: 使用条件编译、平台检测、功能降级等方式,确保在不同平台上的兼容性。
最佳实践建议
网络请求优化
- 合理设置超时时间和重试次数
- 使用请求去重避免重复请求
- 实现请求缓存减少网络开销
- 使用 loading 状态提升用户体验
数据管理
- 建立清晰的数据流向和状态管理
- 合理使用缓存策略提升性能
- 定期清理过期和无用数据
- 实现数据备份和恢复机制
错误处理
- 建立完善的错误处理机制
- 提供友好的错误提示信息
- 实现错误上报和监控
- 添加降级和容错处理
性能优化
- 使用防抖和节流控制请求频率
- 实现数据预加载和懒加载
- 优化数据结构和算法
- 监控和分析性能指标
安全考虑
- 验证和过滤用户输入
- 使用安全的数据传输和存储
- 实现访问控制和权限验证
- 定期更新安全策略
通过本章的学习,你应该能够熟练地在 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>
”`