uni-app 数据请求

核心知识点

网络请求 API

uni-app 提供了 uni.request() 方法用于发起网络请求,支持 GET、POST 等多种请求方式。

1. 基本用法

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

// 发起 POST 请求
uni.request({
  url: 'https://api.example.com/login',
  method: 'POST',
  data: {
    username: 'admin',
    password: '123456'
  },
  success: (res) => {
    console.log('登录成功:', res.data);
  },
  fail: (err) => {
    console.log('登录失败:', err);
  }
});

2. 请求参数

uni.request() 支持以下参数:

  • url: 请求地址
  • method: 请求方法,默认为 GET
  • data: 请求数据
  • header: 请求头
  • dataType: 响应数据类型,默认为 json
  • responseType: 响应类型,默认为 text
  • success: 成功回调
  • fail: 失败回调
  • complete: 完成回调

3. 并发请求

使用 Promise.all() 可以发起并发请求:

Promise.all([
  uni.request({ url: 'https://api.example.com/data1' }),
  uni.request({ url: 'https://api.example.com/data2' })
]).then(([res1, res2]) => {
  console.log('数据1:', res1.data);
  console.log('数据2:', res2.data);
}).catch((err) => {
  console.log('请求失败:', err);
});

请求封装

为了提高代码复用性和可维护性,我们可以封装网络请求。

1. 基础封装

// utils/request.js
const baseURL = 'https://api.example.com';

function request(options) {
  return new Promise((resolve, reject) => {
    uni.request({
      url: baseURL + options.url,
      method: options.method || 'GET',
      data: options.data || {},
      header: {
        'Content-Type': 'application/json',
        ...options.header
      },
      success: (res) => {
        if (res.statusCode === 200) {
          resolve(res.data);
        } else {
          reject(new Error(`请求失败: ${res.statusCode}`));
        }
      },
      fail: (err) => {
        reject(err);
      }
    });
  });
}

// 导出常用方法
export default {
  get(url, data, header) {
    return request({ url, method: 'GET', data, header });
  },
  post(url, data, header) {
    return request({ url, method: 'POST', data, header });
  },
  put(url, data, header) {
    return request({ url, method: 'PUT', data, header });
  },
  delete(url, data, header) {
    return request({ url, method: 'DELETE', data, header });
  }
};

2. 高级封装

添加拦截器和错误处理:

// utils/request.js
const baseURL = 'https://api.example.com';

// 请求队列
let requestQueue = [];
// 是否显示加载中
let loadingCount = 0;

function request(options) {
  return new Promise((resolve, reject) => {
    // 显示加载中
    if (options.loading !== false) {
      showLoading();
    }

    // 请求开始
    const requestTask = uni.request({
      url: baseURL + options.url,
      method: options.method || 'GET',
      data: options.data || {},
      header: {
        'Content-Type': 'application/json',
        'Authorization': uni.getStorageSync('token') || '',
        ...options.header
      },
      success: (res) => {
        // 请求成功处理
        if (res.statusCode === 200) {
          const data = res.data;
          if (data.code === 0) {
            resolve(data.data);
          } else {
            // 业务错误
            uni.showToast({
              title: data.message || '请求失败',
              icon: 'none'
            });
            reject(new Error(data.message || '请求失败'));
          }
        } else if (res.statusCode === 401) {
          // 未授权
          uni.navigateTo({
            url: '/pages/login/login'
          });
          reject(new Error('未授权'));
        } else {
          // 其他错误
          uni.showToast({
            title: `请求失败: ${res.statusCode}`,
            icon: 'none'
          });
          reject(new Error(`请求失败: ${res.statusCode}`));
        }
      },
      fail: (err) => {
        // 网络错误
        uni.showToast({
          title: '网络错误',
          icon: 'none'
        });
        reject(err);
      },
      complete: () => {
        // 隐藏加载中
        if (options.loading !== false) {
          hideLoading();
        }
        // 移除请求任务
        const index = requestQueue.indexOf(requestTask);
        if (index > -1) {
          requestQueue.splice(index, 1);
        }
      }
    });

    // 添加到请求队列
    requestQueue.push(requestTask);
  });
}

// 显示加载中
function showLoading() {
  if (loadingCount === 0) {
    uni.showLoading({
      title: '加载中...',
      mask: true
    });
  }
  loadingCount++;
}

// 隐藏加载中
function hideLoading() {
  loadingCount--;
  if (loadingCount === 0) {
    uni.hideLoading();
  }
}

// 取消所有请求
export function cancelAllRequest() {
  requestQueue.forEach(task => {
    task.abort();
  });
  requestQueue = [];
}

// 导出常用方法
export default {
  get(url, data, header) {
    return request({ url, method: 'GET', data, header });
  },
  post(url, data, header) {
    return request({ url, method: 'POST', data, header });
  },
  put(url, data, header) {
    return request({ url, method: 'PUT', data, header });
  },
  delete(url, data, header) {
    return request({ url, method: 'DELETE', data, header });
  }
};

数据处理

处理网络请求返回的数据,包括数据转换、缓存等。

1. 数据转换

// 处理列表数据
function processListData(data) {
  return data.map(item => ({
    id: item.id,
    title: item.title,
    content: item.content,
    createdAt: formatDate(item.created_at),
    author: {
      id: item.author.id,
      name: item.author.name,
      avatar: item.author.avatar
    }
  }));
}

// 格式化日期
function formatDate(dateString) {
  const date = new Date(dateString);
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
}

2. 数据缓存

使用本地存储缓存数据:

// 缓存数据
function cacheData(key, data, expire = 3600000) {
  uni.setStorageSync(key, {
    data,
    expire: Date.now() + expire
  });
}

// 获取缓存数据
function getCacheData(key) {
  const cached = uni.getStorageSync(key);
  if (!cached) return null;
  
  if (Date.now() > cached.expire) {
    // 缓存过期
    uni.removeStorageSync(key);
    return null;
  }
  
  return cached.data;
}

// 示例:带缓存的请求
async function getDataWithCache(url, data) {
  const cacheKey = `cache_${url}_${JSON.stringify(data)}`;
  
  // 尝试从缓存获取
  const cachedData = getCacheData(cacheKey);
  if (cachedData) {
    return cachedData;
  }
  
  // 发起请求
  const result = await request.get(url, data);
  
  // 缓存数据
  cacheData(cacheKey, result);
  
  return result;
}

实用案例

对接后端 API

1. 用户登录

<template>
  <view class="login-container">
    <view class="form-item">
      <text>用户名</text>
      <input v-model="username" placeholder="请输入用户名" />
    </view>
    <view class="form-item">
      <text>密码</text>
      <input v-model="password" type="password" placeholder="请输入密码" />
    </view>
    <button class="login-button" @click="login">登录</button>
  </view>
</template>

<script>
import request from '../../utils/request';

export default {
  data() {
    return {
      username: '',
      password: ''
    };
  },
  methods: {
    async login() {
      if (!this.username || !this.password) {
        uni.showToast({
          title: '请输入用户名和密码',
          icon: 'none'
        });
        return;
      }

      try {
        const res = await request.post('/login', {
          username: this.username,
          password: this.password
        });

        // 保存 token
        uni.setStorageSync('token', res.token);
        uni.setStorageSync('user', res.user);

        // 登录成功,跳转到首页
        uni.showToast({
          title: '登录成功',
          icon: 'success'
        });
        uni.switchTab({
          url: '/pages/index/index'
        });
      } catch (error) {
        console.error('登录失败:', error);
      }
    }
  }
};
</script>

<style scoped>
.login-container {
  padding: 40rpx;
}

.form-item {
  margin-bottom: 30rpx;
}

.form-item text {
  display: block;
  margin-bottom: 10rpx;
  font-size: 28rpx;
  color: #333;
}

.form-item input {
  width: 100%;
  height: 80rpx;
  border: 1rpx solid #e5e5e5;
  border-radius: 8rpx;
  padding: 0 20rpx;
  font-size: 28rpx;
}

.login-button {
  width: 100%;
  height: 80rpx;
  background-color: #007aff;
  color: #fff;
  border-radius: 8rpx;
  font-size: 32rpx;
  margin-top: 40rpx;
}
</style>

2. 获取列表数据

<template>
  <view class="list-container">
    <view class="list-item" v-for="item in list" :key="item.id">
      <text class="item-title">{{ item.title }}</text>
      <text class="item-content">{{ item.content }}</text>
      <text class="item-time">{{ item.createdAt }}</text>
    </view>
    <view class="loading" v-if="loading">加载中...</view>
    <view class="no-more" v-if="!loading && !hasMore">没有更多数据了</view>
  </view>
</template>

<script>
import request from '../../utils/request';

export default {
  data() {
    return {
      list: [],
      page: 1,
      limit: 10,
      loading: false,
      hasMore: true
    };
  },
  onLoad() {
    this.loadData();
  },
  onReachBottom() {
    if (!this.loading && this.hasMore) {
      this.loadData();
    }
  },
  methods: {
    async loadData() {
      if (this.loading) return;

      this.loading = true;

      try {
        const res = await request.get('/list', {
          page: this.page,
          limit: this.limit
        });

        if (res.length > 0) {
          this.list = [...this.list, ...res];
          this.page++;
          this.hasMore = res.length === this.limit;
        } else {
          this.hasMore = false;
        }
      } catch (error) {
        console.error('获取数据失败:', error);
        uni.showToast({
          title: '获取数据失败',
          icon: 'none'
        });
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>

<style scoped>
.list-container {
  padding: 20rpx;
}

.list-item {
  background-color: #fff;
  border-radius: 8rpx;
  padding: 20rpx;
  margin-bottom: 20rpx;
  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}

.item-title {
  font-size: 32rpx;
  font-weight: bold;
  margin-bottom: 10rpx;
  color: #333;
}

.item-content {
  font-size: 28rpx;
  color: #666;
  margin-bottom: 10rpx;
  line-height: 1.5;
}

.item-time {
  font-size: 24rpx;
  color: #999;
}

.loading {
  text-align: center;
  padding: 20rpx;
  color: #666;
}

.no-more {
  text-align: center;
  padding: 20rpx;
  color: #999;
}
</style>

3. 提交表单数据

<template>
  <view class="form-container">
    <view class="form-item">
      <text>标题</text>
      <input v-model="form.title" placeholder="请输入标题" />
    </view>
    <view class="form-item">
      <text>内容</text>
      <textarea v-model="form.content" placeholder="请输入内容" />
    </view>
    <button class="submit-button" @click="submitForm">提交</button>
  </view>
</template>

<script>
import request from '../../utils/request';

export default {
  data() {
    return {
      form: {
        title: '',
        content: ''
      }
    };
  },
  methods: {
    async submitForm() {
      if (!this.form.title || !this.form.content) {
        uni.showToast({
          title: '请填写完整信息',
          icon: 'none'
        });
        return;
      }

      try {
        await request.post('/create', this.form);

        uni.showToast({
          title: '提交成功',
          icon: 'success'
        });

        // 重置表单
        this.form = {
          title: '',
          content: ''
        };
      } catch (error) {
        console.error('提交失败:', error);
        uni.showToast({
          title: '提交失败',
          icon: 'none'
        });
      }
    }
  }
};
</script>

<style scoped>
.form-container {
  padding: 20rpx;
}

.form-item {
  margin-bottom: 30rpx;
}

.form-item text {
  display: block;
  margin-bottom: 10rpx;
  font-size: 28rpx;
  color: #333;
}

.form-item input {
  width: 100%;
  height: 80rpx;
  border: 1rpx solid #e5e5e5;
  border-radius: 8rpx;
  padding: 0 20rpx;
  font-size: 28rpx;
}

.form-item textarea {
  width: 100%;
  height: 200rpx;
  border: 1rpx solid #e5e5e5;
  border-radius: 8rpx;
  padding: 20rpx;
  font-size: 28rpx;
  resize: none;
}

.submit-button {
  width: 100%;
  height: 80rpx;
  background-color: #007aff;
  color: #fff;
  border-radius: 8rpx;
  font-size: 32rpx;
  margin-top: 40rpx;
}
</style>

学习目标

通过本集的学习,你应该能够:

  1. 掌握 uni-app 网络请求 API 的基本用法,包括 GET、POST 等请求方式
  2. 学会封装网络请求,添加拦截器和错误处理
  3. 熟悉数据处理的方法,包括数据转换和缓存
  4. 能够通过实用案例对接后端 API,实现用户登录、获取列表数据、提交表单等功能
  5. 了解网络请求的最佳实践,提高应用的性能和用户体验

小结

数据请求是 uni-app 开发中的重要部分,合理的请求封装和数据处理可以提高应用的性能和可维护性。通过本集的学习,你已经掌握了网络请求 API、请求封装、数据处理等核心知识点,并通过实际案例了解了如何对接后端 API。在后续的开发中,你可以根据实际需求,灵活运用这些知识,构建更加功能强大的应用。

« 上一篇 uni-app 样式开发 下一篇 » uni-app 本地存储