Skip to content

API调用详解 - 网络请求、本地存储、设备信息

🔌 API是应用的生命线,今天我们来学习如何调用各种API让应用更强大

1. 网络请求

基础请求

javascript
// GET请求
async function getData() {
  try {
    const res = await uni.request({
      url: 'https://api.example.com/users',
      method: 'GET',
      header: {
        'Content-Type': 'application/json'
      }
    })
    
    console.log('请求成功:', res.data)
    return res.data
  } catch (error) {
    console.error('请求失败:', error)
    uni.showToast({
      title: '网络请求失败',
      icon: 'error'
    })
  }
}

// POST请求
async function postData(userData) {
  try {
    const res = await uni.request({
      url: 'https://api.example.com/users',
      method: 'POST',
      header: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + getToken()
      },
      data: userData
    })
    
    return res.data
  } catch (error) {
    console.error('提交失败:', error)
    throw error
  }
}

封装请求工具

javascript
// utils/request.js
class Request {
  constructor() {
    this.baseURL = 'https://api.example.com'
    this.timeout = 10000
    this.header = {
      'Content-Type': 'application/json'
    }
  }
  
  // 请求拦截器
  interceptRequest(config) {
    // 添加token
    const token = uni.getStorageSync('token')
    if (token) {
      config.header.Authorization = 'Bearer ' + token
    }
    
    // 显示加载提示
    uni.showLoading({
      title: '加载中...'
    })
    
    return config
  }
  
  // 响应拦截器
  interceptResponse(response) {
    // 隐藏加载提示
    uni.hideLoading()
    
    // 统一错误处理
    if (response.statusCode !== 200) {
      uni.showToast({
        title: '请求失败',
        icon: 'error'
      })
      throw new Error(`HTTP \${response.statusCode}`)
    }
    
    // 业务错误处理
    if (response.data.code !== 0) {
      uni.showToast({
        title: response.data.message || '操作失败',
        icon: 'error'
      })
      throw new Error(response.data.message)
    }
    
    return response.data
  }
  
  // 通用请求方法
  async request(options) {
    const config = {
      url: this.baseURL + options.url,
      method: options.method || 'GET',
      header: { ...this.header, ...options.header },
      data: options.data,
      timeout: this.timeout
    }
    
    try {
      const interceptedConfig = this.interceptRequest(config)
      const response = await uni.request(interceptedConfig)
      return this.interceptResponse(response)
    } catch (error) {
      uni.hideLoading()
      throw error
    }
  }
  
  // 便捷方法
  get(url, params) {
    return this.request({
      url: params ? `\${url}?\${this.buildQuery(params)}` : url,
      method: 'GET'
    })
  }
  
  post(url, data) {
    return this.request({
      url,
      method: 'POST',
      data
    })
  }
  
  put(url, data) {
    return this.request({
      url,
      method: 'PUT',
      data
    })
  }
  
  delete(url) {
    return this.request({
      url,
      method: 'DELETE'
    })
  }
  
  // 构建查询字符串
  buildQuery(params) {
    return Object.keys(params)
      .map(key => `\${encodeURIComponent(key)}=\${encodeURIComponent(params[key])}`)
      .join('&')
  }
}

// 创建实例
const request = new Request()

export default request

使用封装的请求

vue
<template>
  <view class="api-demo">
    <button @click="loadUsers">获取用户列表</button>
    <button @click="createUser">创建用户</button>
    
    <view v-for="user in users" :key="user.id" class="user-item">
      <text>\{\{ user.name \}\} - \{\{ user.email \}\}</text>
    </view>
  </view>
</template>

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

export default {
  data() {
    return {
      users: []
    }
  },
  
  methods: {
    async loadUsers() {
      try {
        const result = await request.get('/users', {
          page: 1,
          limit: 10
        })
        this.users = result.data
      } catch (error) {
        console.error('加载用户失败:', error)
      }
    },
    
    async createUser() {
      try {
        const newUser = {
          name: '新用户',
          email: 'new@example.com'
        }
        
        const result = await request.post('/users', newUser)
        this.users.push(result.data)
        
        uni.showToast({
          title: '创建成功',
          icon: 'success'
        })
      } catch (error) {
        console.error('创建用户失败:', error)
      }
    }
  }
}
</script>

2. 本地存储

同步存储

javascript
// 存储数据
uni.setStorageSync('userInfo', {
  id: 1,
  name: '张三',
  avatar: '/static/avatar.jpg'
})

// 读取数据
const userInfo = uni.getStorageSync('userInfo')
console.log('用户信息:', userInfo)

// 删除数据
uni.removeStorageSync('userInfo')

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

// 获取存储信息
const storageInfo = uni.getStorageInfoSync()
console.log('存储信息:', storageInfo)

异步存储

javascript
// 异步存储
async function saveUserData(userData) {
  try {
    await uni.setStorage({
      key: 'userData',
      data: userData
    })
    console.log('保存成功')
  } catch (error) {
    console.error('保存失败:', error)
  }
}

// 异步读取
async function getUserData() {
  try {
    const res = await uni.getStorage({
      key: 'userData'
    })
    return res.data
  } catch (error) {
    console.error('读取失败:', error)
    return null
  }
}

存储管理工具

javascript
// utils/storage.js
class Storage {
  // 设置过期时间的存储
  setWithExpiry(key, value, expiryMinutes = 60) {
    const now = new Date()
    const item = {
      value: value,
      expiry: now.getTime() + (expiryMinutes * 60 * 1000)
    }
    uni.setStorageSync(key, item)
  }
  
  // 获取带过期时间的存储
  getWithExpiry(key) {
    const itemStr = uni.getStorageSync(key)
    if (!itemStr) {
      return null
    }
    
    const item = itemStr
    const now = new Date()
    
    if (now.getTime() > item.expiry) {
      uni.removeStorageSync(key)
      return null
    }
    
    return item.value
  }
  
  // 存储用户信息
  setUserInfo(userInfo) {
    uni.setStorageSync('userInfo', userInfo)
  }
  
  // 获取用户信息
  getUserInfo() {
    return uni.getStorageSync('userInfo') || {}
  }
  
  // 清除用户信息
  clearUserInfo() {
    uni.removeStorageSync('userInfo')
    uni.removeStorageSync('token')
  }
  
  // 存储token
  setToken(token) {
    uni.setStorageSync('token', token)
  }
  
  // 获取token
  getToken() {
    return uni.getStorageSync('token')
  }
  
  // 检查是否登录
  isLoggedIn() {
    return !!this.getToken()
  }
}

const storage = new Storage()
export default storage

3. 设备信息

获取系统信息

javascript
// 获取系统信息
function getSystemInfo() {
  const systemInfo = uni.getSystemInfoSync()
  
  console.log('设备信息:', {
    brand: systemInfo.brand,           // 设备品牌
    model: systemInfo.model,           // 设备型号
    system: systemInfo.system,         // 操作系统
    platform: systemInfo.platform,    // 客户端平台
    version: systemInfo.version,       // 微信版本号
    SDKVersion: systemInfo.SDKVersion, // 基础库版本
    screenWidth: systemInfo.screenWidth,   // 屏幕宽度
    screenHeight: systemInfo.screenHeight, // 屏幕高度
    windowWidth: systemInfo.windowWidth,   // 可使用窗口宽度
    windowHeight: systemInfo.windowHeight, // 可使用窗口高度
    statusBarHeight: systemInfo.statusBarHeight, // 状态栏高度
    safeArea: systemInfo.safeArea      // 安全区域
  })
  
  return systemInfo
}

// 异步获取
async function getSystemInfoAsync() {
  try {
    const systemInfo = await uni.getSystemInfo()
    return systemInfo
  } catch (error) {
    console.error('获取系统信息失败:', error)
  }
}

网络状态

javascript
// 获取网络类型
function getNetworkType() {
  uni.getNetworkType({
    success: (res) => {
      console.log('网络类型:', res.networkType)
      // wifi, 2g, 3g, 4g, 5g, unknown, none
      
      if (res.networkType === 'none') {
        uni.showToast({
          title: '网络连接失败',
          icon: 'error'
        })
      }
    }
  })
}

// 监听网络状态变化
function watchNetworkStatus() {
  uni.onNetworkStatusChange((res) => {
    console.log('网络状态变化:', res)
    
    if (!res.isConnected) {
      uni.showToast({
        title: '网络已断开',
        icon: 'error'
      })
    } else {
      uni.showToast({
        title: '网络已连接',
        icon: 'success'
      })
    }
  })
}

位置信息

javascript
// 获取当前位置
async function getCurrentLocation() {
  try {
    const res = await uni.getLocation({
      type: 'gcj02', // 坐标系类型
      altitude: true // 传入 true 会返回高度信息
    })
    
    console.log('位置信息:', {
      latitude: res.latitude,   // 纬度
      longitude: res.longitude, // 经度
      speed: res.speed,         // 速度
      accuracy: res.accuracy,   // 位置精度
      altitude: res.altitude,   // 高度
      verticalAccuracy: res.verticalAccuracy, // 垂直精度
      horizontalAccuracy: res.horizontalAccuracy // 水平精度
    })
    
    return res
  } catch (error) {
    console.error('获取位置失败:', error)
    
    if (error.errMsg.includes('auth deny')) {
      uni.showModal({
        title: '提示',
        content: '需要获取您的地理位置,请确认授权',
        success: (res) => {
          if (res.confirm) {
            uni.openSetting()
          }
        }
      })
    }
  }
}

// 选择位置
function chooseLocation() {
  uni.chooseLocation({
    success: (res) => {
      console.log('选择的位置:', {
        name: res.name,
        address: res.address,
        latitude: res.latitude,
        longitude: res.longitude
      })
    },
    fail: (error) => {
      console.error('选择位置失败:', error)
    }
  })
}

4. 实战案例:用户管理页面

vue
<template>
  <view class="user-manager">
    <!-- 系统信息 -->
    <view class="info-section">
      <text class="section-title">设备信息</text>
      <view class="info-item">
        <text>设备型号:\{\{ deviceInfo.model \}\}</text>
      </view>
      <view class="info-item">
        <text>系统版本:\{\{ deviceInfo.system \}\}</text>
      </view>
      <view class="info-item">
        <text>网络状态:\{\{ networkStatus \}\}</text>
      </view>
    </view>
    
    <!-- 用户信息 -->
    <view class="info-section">
      <text class="section-title">用户信息</text>
      <view v-if="userInfo.id" class="user-card">
        <image :src="userInfo.avatar" class="avatar" />
        <view class="user-details">
          <text class="username">\{\{ userInfo.name \}\}</text>
          <text class="user-email">\{\{ userInfo.email \}\}</text>
        </view>
        <button @click="logout" size="mini">退出登录</button>
      </view>
      <view v-else class="login-prompt">
        <text>未登录</text>
        <button @click="login" type="primary" size="mini">立即登录</button>
      </view>
    </view>
    
    <!-- 操作按钮 -->
    <view class="action-section">
      <button @click="refreshData">刷新数据</button>
      <button @click="clearCache">清除缓存</button>
      <button @click="getLocation">获取位置</button>
    </view>
    
    <!-- 位置信息 -->
    <view v-if="locationInfo" class="info-section">
      <text class="section-title">位置信息</text>
      <view class="info-item">
        <text>纬度:\{\{ locationInfo.latitude \}\}</text>
      </view>
      <view class="info-item">
        <text>经度:\{\{ locationInfo.longitude \}\}</text>
      </view>
      <view class="info-item">
        <text>精度:\{\{ locationInfo.accuracy \}\}米</text>
      </view>
    </view>
  </view>
</template>

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

export default {
  data() {
    return {
      deviceInfo: {},
      networkStatus: '未知',
      userInfo: {},
      locationInfo: null
    }
  },
  
  onLoad() {
    this.initData()
  },
  
  methods: {
    async initData() {
      // 获取设备信息
      this.deviceInfo = uni.getSystemInfoSync()
      
      // 获取网络状态
      this.getNetworkStatus()
      
      // 获取用户信息
      this.userInfo = storage.getUserInfo()
      
      // 监听网络状态变化
      this.watchNetwork()
    },
    
    getNetworkStatus() {
      uni.getNetworkType({
        success: (res) => {
          this.networkStatus = res.networkType
        }
      })
    },
    
    watchNetwork() {
      uni.onNetworkStatusChange((res) => {
        this.networkStatus = res.networkType
        
        if (!res.isConnected) {
          uni.showToast({
            title: '网络已断开',
            icon: 'error'
          })
        }
      })
    },
    
    async login() {
      try {
        // 模拟登录
        const loginData = {
          username: 'demo',
          password: '123456'
        }
        
        const result = await request.post('/auth/login', loginData)
        
        // 保存用户信息和token
        storage.setUserInfo(result.user)
        storage.setToken(result.token)
        
        this.userInfo = result.user
        
        uni.showToast({
          title: '登录成功',
          icon: 'success'
        })
      } catch (error) {
        console.error('登录失败:', error)
      }
    },
    
    logout() {
      uni.showModal({
        title: '确认退出',
        content: '确定要退出登录吗?',
        success: (res) => {
          if (res.confirm) {
            storage.clearUserInfo()
            this.userInfo = {}
            
            uni.showToast({
              title: '已退出登录',
              icon: 'success'
            })
          }
        }
      })
    },
    
    async refreshData() {
      try {
        if (!storage.isLoggedIn()) {
          uni.showToast({
            title: '请先登录',
            icon: 'error'
          })
          return
        }
        
        const userInfo = await request.get('/user/profile')
        storage.setUserInfo(userInfo)
        this.userInfo = userInfo
        
        uni.showToast({
          title: '刷新成功',
          icon: 'success'
        })
      } catch (error) {
        console.error('刷新失败:', error)
      }
    },
    
    clearCache() {
      uni.showModal({
        title: '清除缓存',
        content: '确定要清除所有缓存数据吗?',
        success: (res) => {
          if (res.confirm) {
            uni.clearStorageSync()
            this.userInfo = {}
            
            uni.showToast({
              title: '缓存已清除',
              icon: 'success'
            })
          }
        }
      })
    },
    
    async getLocation() {
      try {
        const location = await uni.getLocation({
          type: 'gcj02'
        })
        
        this.locationInfo = location
        
        uni.showToast({
          title: '位置获取成功',
          icon: 'success'
        })
      } catch (error) {
        console.error('获取位置失败:', error)
        
        if (error.errMsg.includes('auth deny')) {
          uni.showModal({
            title: '权限申请',
            content: '需要获取您的位置信息,请在设置中开启位置权限',
            success: (res) => {
              if (res.confirm) {
                uni.openSetting()
              }
            }
          })
        }
      }
    }
  }
}
</script>

<style>
.user-manager {
  padding: 20px;
}
.info-section {
  margin-bottom: 30px;
  background: white;
  border-radius: 8px;
  padding: 15px;
}
.section-title {
  font-size: 18px;
  font-weight: bold;
  margin-bottom: 15px;
  display: block;
}
.info-item {
  padding: 8px 0;
  border-bottom: 1px solid #f0f0f0;
}
.user-card {
  display: flex;
  align-items: center;
}
.avatar {
  width: 50px;
  height: 50px;
  border-radius: 25px;
  margin-right: 15px;
}
.user-details {
  flex: 1;
}
.username {
  font-size: 16px;
  font-weight: bold;
  display: block;
  margin-bottom: 5px;
}
.user-email {
  color: #666;
  font-size: 14px;
}
.login-prompt {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.action-section button {
  margin-bottom: 10px;
  width: 100%;
}
</style>

小结

今天我们学习了:

  • ✅ 网络请求的基础用法和封装
  • ✅ 本地存储的同步和异步操作
  • ✅ 设备信息和网络状态获取
  • ✅ 位置信息的获取和处理
  • ✅ 实战案例:用户管理页面

API使用要点

  • 合理封装请求工具提高开发效率
  • 注意错误处理和用户体验
  • 谨慎使用设备权限,做好权限申请
  • 本地存储要考虑数据安全和过期

下一篇预告

下一篇我们将学习《自定义组件开发 - 封装你自己的组件》,学习如何创建可复用的组件。


API是连接应用与外界的桥梁,掌握了API的使用,你的应用就能拥有无限可能!