uni-app 云开发实战

核心知识点

1. 云开发架构设计

云开发架构是基于 uniCloud 的全栈解决方案,主要包含以下几个核心组件:

  • 云函数:处理后端业务逻辑,支持 Node.js 运行环境
  • 云数据库:基于 MongoDB 的 NoSQL 数据库,支持复杂查询
  • 云存储:用于存储用户上传的文件,如图片、视频等
  • 前端应用:基于 uni-app 开发的跨平台应用

2. 前后端分离实现

在云开发架构中,前后端分离主要通过以下方式实现:

  • 前端:负责 UI 渲染、用户交互、数据展示
  • 云函数:作为后端 API,处理业务逻辑、数据处理、第三方服务集成
  • 数据交互:通过 uniCloud SDK 进行前后端通信

3. 部署流程

完整的云开发部署流程包括:

  1. 本地开发:在 HBuilderX 中编写前端代码和云函数
  2. 云函数部署:将云函数上传到云端运行环境
  3. 数据库初始化:创建集合、设置索引、导入初始数据
  4. 存储配置:设置存储桶权限、配置 CDN
  5. 前端发布:将前端应用打包发布到各个平台

实用案例分析

案例:构建一个完整的云开发应用

我们将构建一个简单的任务管理应用,包含以下功能:

  • 用户注册登录
  • 任务创建、编辑、删除
  • 任务状态管理
  • 任务列表查询

1. 项目初始化

  1. 创建 uni-app 项目:在 HBuilderX 中创建新项目,选择 "uni-app 云开发" 模板
  2. 关联云服务空间:在项目中关联一个 uniCloud 服务空间

2. 云函数开发

创建以下云函数:

user-register 云函数

'use strict';

exports.main = async (event, context) => {
  const db = uniCloud.database();
  const { username, password } = event;
  
  // 检查用户是否已存在
  const userExists = await db.collection('users')
    .where({ username })
    .get();
  
  if (userExists.data.length > 0) {
    return { success: false, message: '用户已存在' };
  }
  
  // 创建新用户
  const result = await db.collection('users').add({
    username,
    password: uniCloud.md5(password), // 简单加密,实际项目中应使用更安全的方式
    createTime: new Date()
  });
  
  return { success: true, message: '注册成功', userId: result.id };
};

user-login 云函数

'use strict';

exports.main = async (event, context) => {
  const db = uniCloud.database();
  const { username, password } = event;
  
  // 查询用户
  const user = await db.collection('users')
    .where({ 
      username, 
      password: uniCloud.md5(password) 
    })
    .get();
  
  if (user.data.length === 0) {
    return { success: false, message: '用户名或密码错误' };
  }
  
  return { 
    success: true, 
    message: '登录成功', 
    userInfo: {
      id: user.data[0]._id,
      username: user.data[0].username
    }
  };
};

task-crud 云函数

'use strict';

exports.main = async (event, context) => {
  const db = uniCloud.database();
  const { action, taskId, taskData, userId } = event;
  
  switch (action) {
    case 'create':
      // 创建任务
      const createResult = await db.collection('tasks').add({
        ...taskData,
        userId,
        createTime: new Date(),
        status: 'pending'
      });
      return { success: true, message: '任务创建成功', taskId: createResult.id };
      
    case 'update':
      // 更新任务
      await db.collection('tasks')
        .doc(taskId)
        .update(taskData);
      return { success: true, message: '任务更新成功' };
      
    case 'delete':
      // 删除任务
      await db.collection('tasks')
        .doc(taskId)
        .remove();
      return { success: true, message: '任务删除成功' };
      
    case 'getList':
      // 获取任务列表
      const tasks = await db.collection('tasks')
        .where({ userId })
        .orderBy('createTime', 'desc')
        .get();
      return { success: true, data: tasks.data };
      
    default:
      return { success: false, message: '无效的操作' };
  }
};

3. 前端应用开发

登录页面 (pages/login/login.vue):

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

<script>
export default {
  data() {
    return {
      form: {
        username: '',
        password: ''
      }
    };
  },
  methods: {
    async login() {
      const result = await uniCloud.callFunction({
        name: 'user-login',
        data: this.form
      });
      
      if (result.result.success) {
        uni.setStorageSync('userInfo', result.result.userInfo);
        uni.switchTab({ url: '/pages/task/task' });
      } else {
        uni.showToast({ title: result.result.message, icon: 'none' });
      }
    },
    async register() {
      const result = await uniCloud.callFunction({
        name: 'user-register',
        data: this.form
      });
      
      if (result.result.success) {
        uni.showToast({ title: '注册成功,请登录', icon: 'success' });
      } else {
        uni.showToast({ title: result.result.message, icon: 'none' });
      }
    }
  }
};
</script>

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

.login-form {
  background-color: #fff;
  padding: 40rpx;
  border-radius: 10rpx;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}

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

.form-item input {
  border: 1rpx solid #ddd;
  border-radius: 5rpx;
  padding: 20rpx;
  font-size: 32rpx;
}

.form-actions {
  margin-top: 40rpx;
}

.form-actions button {
  margin-bottom: 20rpx;
}
</style>

任务列表页面 (pages/task/task.vue):

<template>
  <view class="task-container">
    <view class="task-header">
      <text class="task-title">任务管理</text>
      <button @click="addTask" type="primary" size="mini">新建任务</button>
    </view>
    
    <view class="task-list">
      <view v-for="task in tasks" :key="task._id" class="task-item">
        <view class="task-info">
          <text class="task-name">{{ task.name }}</text>
          <text class="task-status">{{ task.status === 'pending' ? '待完成' : '已完成' }}</text>
        </view>
        <view class="task-actions">
          <button @click="editTask(task)" type="default" size="mini">编辑</button>
          <button @click="deleteTask(task._id)" type="warn" size="mini">删除</button>
          <button @click="toggleStatus(task)" type="primary" size="mini">
            {{ task.status === 'pending' ? '完成' : '重置' }}
          </button>
        </view>
      </view>
    </view>
    
    <!-- 新建/编辑任务弹窗 -->
    <uni-popup v-model="popupVisible" mode="bottom">
      <view class="popup-content">
        <view class="popup-header">
          <text>{{ editingTask ? '编辑任务' : '新建任务' }}</text>
          <text @click="popupVisible = false" class="popup-close">×</text>
        </view>
        <view class="popup-body">
          <input v-model="taskForm.name" placeholder="请输入任务名称" />
          <textarea v-model="taskForm.description" placeholder="请输入任务描述" />
        </view>
        <view class="popup-footer">
          <button @click="popupVisible = false" type="default">取消</button>
          <button @click="saveTask" type="primary">保存</button>
        </view>
      </view>
    </uni-popup>
  </view>
</template>

<script>
export default {
  data() {
    return {
      tasks: [],
      popupVisible: false,
      editingTask: null,
      taskForm: {
        name: '',
description: ''
      }
    };
  },
  onLoad() {
    this.getTasks();
  },
  methods: {
    async getTasks() {
      const userInfo = uni.getStorageSync('userInfo');
      if (!userInfo) {
        uni.navigateTo({ url: '/pages/login/login' });
        return;
      }
      
      const result = await uniCloud.callFunction({
        name: 'task-crud',
        data: {
          action: 'getList',
          userId: userInfo.id
        }
      });
      
      if (result.result.success) {
        this.tasks = result.result.data;
      }
    },
    
    addTask() {
      this.editingTask = null;
      this.taskForm = { name: '', description: '' };
      this.popupVisible = true;
    },
    
    editTask(task) {
      this.editingTask = task;
      this.taskForm = { ...task };
      this.popupVisible = true;
    },
    
    async saveTask() {
      const userInfo = uni.getStorageSync('userInfo');
      
      if (this.editingTask) {
        // 更新任务
        const result = await uniCloud.callFunction({
          name: 'task-crud',
          data: {
            action: 'update',
            taskId: this.editingTask._id,
            taskData: this.taskForm
          }
        });
        
        if (result.result.success) {
          uni.showToast({ title: '任务更新成功', icon: 'success' });
          this.getTasks();
          this.popupVisible = false;
        }
      } else {
        // 创建任务
        const result = await uniCloud.callFunction({
          name: 'task-crud',
          data: {
            action: 'create',
            taskData: this.taskForm,
            userId: userInfo.id
          }
        });
        
        if (result.result.success) {
          uni.showToast({ title: '任务创建成功', icon: 'success' });
          this.getTasks();
          this.popupVisible = false;
        }
      }
    },
    
    async deleteTask(taskId) {
      uni.showModal({
        title: '确认删除',
        content: '确定要删除这个任务吗?',
        success: async (res) => {
          if (res.confirm) {
            const result = await uniCloud.callFunction({
              name: 'task-crud',
              data: {
                action: 'delete',
                taskId
              }
            });
            
            if (result.result.success) {
              uni.showToast({ title: '任务删除成功', icon: 'success' });
              this.getTasks();
            }
          }
        }
      });
    },
    
    async toggleStatus(task) {
      const newStatus = task.status === 'pending' ? 'completed' : 'pending';
      const result = await uniCloud.callFunction({
        name: 'task-crud',
        data: {
          action: 'update',
          taskId: task._id,
          taskData: { status: newStatus }
        }
      });
      
      if (result.result.success) {
        uni.showToast({ title: '任务状态更新成功', icon: 'success' });
        this.getTasks();
      }
    }
  }
};
</script>

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

.task-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 30rpx;
}

.task-title {
  font-size: 36rpx;
  font-weight: bold;
}

.task-list {
  background-color: #fff;
  border-radius: 10rpx;
  overflow: hidden;
}

.task-item {
  padding: 20rpx;
  border-bottom: 1rpx solid #eee;
}

.task-info {
  display: flex;
  justify-content: space-between;
  margin-bottom: 15rpx;
}

.task-name {
  font-size: 32rpx;
  font-weight: 500;
}

.task-status {
  font-size: 28rpx;
  color: #999;
}

.task-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10rpx;
}

.popup-content {
  background-color: #fff;
  border-radius: 20rpx 20rpx 0 0;
  padding: 20rpx;
}

.popup-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 30rpx;
}

.popup-close {
  font-size: 40rpx;
  color: #999;
}

.popup-body input,
.popup-body textarea {
  border: 1rpx solid #ddd;
  border-radius: 5rpx;
  padding: 20rpx;
  margin-bottom: 20rpx;
  width: 100%;
}

.popup-footer {
  display: flex;
  gap: 20rpx;
  margin-top: 30rpx;
}

.popup-footer button {
  flex: 1;
}
</style>

4. 部署与发布

  1. 部署云函数:在 HBuilderX 中,右键点击 cloudfunctions 目录,选择 "上传所有云函数"。

  2. 数据库初始化:在 uniCloud Web 控制台中,创建 userstasks 集合。

  3. 前端发布

    • 在 HBuilderX 中,选择 "发行" -> "网站 - Web 前端"
    • 填写应用名称和版本号
    • 点击 "发行" 按钮,生成 Web 版前端代码
    • 将生成的代码部署到静态网站托管服务

学习目标

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

  1. 掌握云开发架构:理解 uniCloud 的核心组件和工作原理
  2. 实现前后端分离:通过云函数和前端应用的配合,构建完整的前后端分离架构
  3. 熟悉部署流程:掌握从开发到部署的完整流程
  4. 构建全栈应用:能够独立开发和部署基于云开发的完整应用

总结

uni-app 云开发提供了一种全新的全栈开发模式,让开发者可以专注于业务逻辑的实现,而无需关心服务器运维、数据库管理等底层问题。通过云函数、云数据库和云存储的配合,我们可以快速构建出功能完整、性能可靠的应用。

在实际项目中,我们还可以结合更多的云开发能力,如:

  • 云调用:直接调用微信、支付宝等平台的开放能力
  • 定时触发:设置定时任务,执行周期性操作
  • HTTP 触发:通过 HTTP 请求触发云函数,实现 RESTful API
  • 消息推送:集成推送服务,实现消息通知功能

通过不断探索和实践,你将能够充分发挥 uni-app 云开发的优势,构建出更加复杂和强大的应用。

« 上一篇 uni-app 云存储使用 下一篇 » uni-app 小程序分包加载