[前后端系统开发教程]第四节-前端多平台部署的终极解决方案
在上一节中我们已经制作了一个简单的用户管理后端系统,我们这节就来尝试制作一个对应的前端系统。那么,我们是要使用安卓开发者工具制作一个安卓app,或者部署为微信小程序,亦或部署为传统的html网页?
答案是我全都要!通过DCloud生态,我们可以实现一份代码,多端部署。
第一部分:什么是DCloud生态?
众将士多端露难色,新面孔竟生好胆识
注:本节开始,教程的节奏会适当加快,希望各位可以跟上。
简单来说,DCloud生态的核心功能是,通过将项目按照不同的目标部署平台,二次编译为对应平台的代码,以实现“一份代码,多端部署”,以提高开发效率。详细介绍请参考uniapp官方文档:简介 - HBuilderX 文档。DCloud还提供云函数、云对象等工具,我们将在教程的后面去学习。
在这节教程中我们先学习如何在HBuilderX中调用上节中后端系统的API(即后端服务接口),编写一份前端代码,再将其打包为微信小程序、html网页和安卓app。
第二部分:怎么调用后端API接口?
接口表叫那前端瞧,服务器知晓谁来还
我们先回顾一下上节教程中的接口类,将其整理为一份API接口说明文件:
用户管理接口文档 1 获取所有用户 URL GET /users 描述 获取系统中所有用户的列表 响应示例 [ { "userId": 1, "userName": "张三", "password": "123456" }, { "userId": 2, "userName": "李四", "password": "abcdef" } ] 2 根据ID获取用户 URL GET /users/{id} 描述 根据用户ID获取特定用户的信息 参数 id 用户的唯一标识符 响应示例 { "userId": 1, "userName": "张三", "password": "123456" } 3 添加用户 URL POST /users 描述 创建一个新的用户 请求体 { "userName": "王五", "password": "qwerty" } 响应示例 { "userId": 3, "userName": "王五", "password": "qwerty" } 4 更新用户 URL PUT /users/{id} 描述 更新指定ID的用户信息 参数 id 要更新的用户的唯一标识符 请求体 { "userName": "王五", "password": "newpassword" } 响应示例 { "userId": 3, "userName": "王五", "password": "newpassword" } 5 删除用户 URL DELETE /users/{id} 描述 删除指定ID的用户 参数 id 要删除的用户的唯一标识符 响应示例 无内容 6 初始化测试数据 URL POST /users/init 描述 为系统添加一些初始测试用户数据 响应示例 { "message": "测试数据已成功添加" }我们在编写前端功能时,就参考这个接口文档,将前端的请求发送给对应的接口,后端就会发相应的json格式响应体返回给前端。
在HBuilderX安装完成后,新建一个uniapp项目。此过程就和操作vs code等IDE大同小异,这里不再赘述,如有问题请参考DCloud的相关文档。
接下来我们要熟悉一下uniapp的项目架构:

我们从上往下介绍:
1.pages目录:各前端页面的vue文件。
2.static目录:放置css样式库、背景图片、icon等静态资源。
3.app.vue:uni-app 项目的根组件文件,类似于 Vue.js 中的根实例。它定义了整个应用的生命周期钩子函数。用于全局逻辑控制,例如初始化数据、监听应用状态变化等。
4.index.html:uni-app 项目的入口 HTML 文件,负责加载主应用脚本并渲染页面。
5.main.js:是 uni-app 项目的核心启动文件,根据不同的 Vue 版本(Vue 2 或 Vue 3)执行相应的初始化逻辑。
6.manifest.json:用于不同平台的图标、发布信息等等发布相关参数的配置。
7.pages.json:配置前端页面的页面路由。
8.uni.promisify.adaptor.js:简化异步调用,提升代码可读性和维护性,尤其适用于需要大量异步操作的场景。
9.uni.scss:一个pro版的css样式库,可以理解为“super css”。
接下来,我们按照接口文档编写功能代码:
我们就像写普通的vue一样搭建一个简单的标签页:
<template> <view> <view> <text>用户管理</text> <button @click="fetchUsers">刷新</button> <button @click="initTestData">初始化测试数据</button> </view> <view> <view v-for="user in users" :key="user.userId"> <text>{{ user.userName }}</text> <view> <button @click="editUser(user)">编辑</button> <button @click="deleteUser(user.userId)">删除</button> </view> </view> </view> <view> <input v-model="newUser.userName" placeholder="用户名" /> <input v-model="newUser.password" placeholder="密码" type="password" /> <button @click="addUser">添加用户</button> </view> <!-- 编辑用户弹窗 --> <view v-if="editingUser"> <view> <input v-model="editingUser.userName" placeholder="用户名" /> <input v-model="editingUser.password" placeholder="密码" type="password" /> <button @click="updateUser">保存</button> <button @click="cancelEdit">取消</button> </view> </view> </view> </template>接着是click事件触发的功能函数:
<script> // 引入Vue 3的ref函数用于创建响应式数据 import { ref } from 'vue' export default { setup() { // 响应式数据定义 const users = ref([]) // 存储用户列表数据 const newUser = ref({ userName: '', // 新用户用户名 password: '' // 新用户密码 }) const editingUser = ref(null) // 当前正在编辑的用户数据 // 获取所有用户列表的异步函数 const fetchUsers = async () => { try { // 发送GET请求获取用户列表 const res = await uni.request({ url: 'http://localhost:8080/api/users', method: 'GET' }) // 请求成功,更新用户列表数据 if (res.statusCode === 200) { users.value = res.data } } catch (error) { // 请求失败,打印错误信息并显示提示 console.error('获取用户列表失败:', error) uni.showToast({ title: '获取用户列表失败', icon: 'none' }) } } // 添加新用户的异步函数 const addUser = async () => { // 验证输入数据完整性 if (!newUser.value.userName || !newUser.value.password) { uni.showToast({ title: '请输入完整信息', icon: 'none' }) return } try { // 发送POST请求添加新用户 const res = await uni.request({ url: 'http://localhost:8080/api/users', method: 'POST', data: newUser.value // 发送用户输入的数据 }) // 请求成功,显示成功提示并刷新列表 if (res.statusCode === 200) { uni.showToast({ title: '添加成功', icon: 'success' }) // 清空输入框 newUser.value = { userName: '', password: '' } // 重新获取用户列表 fetchUsers() } } catch (error) { // 请求失败,显示错误提示 console.error('添加用户失败:', error) uni.showToast({ title: '添加用户失败', icon: 'none' }) } } // 编辑用户函数,设置当前编辑的用户数据 const editUser = (user) => { editingUser.value = { ...user } // 使用展开运算符创建副本,避免直接修改原数据 } // 更新用户信息的异步函数 const updateUser = async () => { try { // 发送PUT请求更新用户信息 const res = await uni.request({ url: `http://localhost:8080/api/users/${editingUser.value.userId}`, method: 'PUT', data: { userName: editingUser.value.userName, password: editingUser.value.password } }) // 请求成功,显示提示并刷新数据 if (res.statusCode === 200) { uni.showToast({ title: '更新成功', icon: 'success' }) cancelEdit() // 关闭编辑弹窗 fetchUsers() // 刷新用户列表 } } catch (error) { // 请求失败,显示错误提示 console.error('更新用户失败:', error) uni.showToast({ title: '更新用户失败', icon: 'none' }) } } // 取消编辑函数,清空编辑状态 const cancelEdit = () => { editingUser.value = null } // 删除用户的异步函数 const deleteUser = async (userId) => { // 显示确认对话框 uni.showModal({ title: '确认删除', content: '确定要删除这个用户吗?', success: async (res) => { // 用户确认删除 if (res.confirm) { try { // 发送DELETE请求删除用户 const response = await uni.request({ url: `http://localhost:8080/api/users/${userId}`, method: 'DELETE' }) // 请求成功,显示提示并刷新列表 if (response.statusCode === 200) { uni.showToast({ title: '删除成功', icon: 'success' }) fetchUsers() } } catch (error) { // 请求失败,显示错误提示 console.error('删除用户失败:', error) uni.showToast({ title: '删除用户失败', icon: 'none' }) } } } }) } // 初始化测试数据的异步函数 const initTestData = async () => { try { // 发送POST请求初始化测试数据 const res = await uni.request({ url: 'http://localhost:8080/api/users/init', method: 'POST' }) // 请求成功,显示提示并刷新列表 if (res.statusCode === 200) { uni.showToast({ title: '测试数据初始化成功', icon: 'success' }) fetchUsers() } } catch (error) { // 请求失败,显示错误提示 console.error('初始化测试数据失败:', error) uni.showToast({ title: '初始化测试数据失败', icon: 'none' }) } } // 页面加载时自动获取用户列表 fetchUsers() // 返回需要在模板中使用的数据和方法 return { users, newUser, editingUser, fetchUsers, addUser, editUser, updateUser, cancelEdit, deleteUser, initTestData } } } </script> 写好代码后,我们就可以测试运行了,一定要先运行后端,再运行前端。
注:在浏览器上运行时,可能会遇到跨域问题,这需要在后端增加一个配置文件Webconfig.java:
package springdemo.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:5173") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .allowCredentials(true); } }第三部分:如何实现一份代码,多端部署?
打包机妙计讨人爱,前后端直呼甚奇巧
在“运行”中我们看见:

各位按需要在相应平台上运行即可,在小程序模拟器上运行会打开微信小程序开发工具,在第一次使用时可能需要先注册账号;在模拟器上运行则需要下载模拟器,各位见招拆招即可。
这里仅展示网页端效果:

第四部分:对于使用HBuilder开发流的其他建议
执神器终有难题现,知底层方才解桎梏
需要注意的是,由于HBuilderX是在微信小程序开发、安卓开发、IOS开发、web开发之上的又一层套娃,使用HBuilderX会遇到比常规开发工具更加层出不穷的平台兼容性问题(例如上文提及的跨域问题),这需要各位开发者具备很强的错误排查能力。
这里我将针对本节教程,将各位可能会遇到的问题简要整理如下:
1.跨越问题:spring后端可能会因CORS阻挡访问,需要重写一个配置文件允许来自local的跨域请求访问端口的文件,代码已在上文给出。
2.web运行正常,但安卓app运行白屏且无报错:这是导入了安卓app环境下无法解析的库,需要更换相应的库。
3.无法在微信小程序开发工具上运行:可能是manifest.json中的微信小程序appid配置错误,建议参考微信小程序开发者官方文档进行进一步排查。
4.预览功能中某些请求响应异常:预览功能是按需编译的,发送请求时可能会发生各种稀奇古怪的BUG,建议预览功能仅供调试样式时使用。