UniApp 博客项目实战:从零到一搭建完整移动端博客应用【全流程详解】
2026/6/30 15:15:57 网站建设 项目流程

摘要

本文基于 UniApp 框架,完整拆解 Blogs 博客移动端项目的开发全流程,涵盖项目初始化、静态页面搭建、网络请求封装、图标资源引入、搜索功能实现、缓存优化、路由跳转、登录注册、个人中心、数据可视化、博客发布等核心模块。文章兼顾原理讲解与代码实现,语言通俗易懂,步骤清晰可复现,适合前端入门开发者作为 UniApp 实战练手项目。

关键词:UniApp;博客项目;网络请求封装;移动端开发;前端实战


1. 项目整体介绍

本项目是一个适配多端的移动端博客应用,基于 Vue.js + UniApp 开发,可编译为微信小程序、H5、App 等多端产物。项目包含博客浏览、搜索、详情查看、评论、用户登录注册、个人中心、数据统计、博客发布等完整功能,覆盖移动端开发的绝大多数核心场景。

技术栈

  • 核心框架:UniApp + Vue 2.x
  • 开发工具:HBuilderX
  • 网络请求:uni.request 二次封装
  • 图标资源:Iconfont 阿里图标库
  • 数据可视化:uCharts
  • 数据存储:UniApp 本地缓存 Storage

2. 项目开发全流程

2.1 创建和初始化项目

我们使用官方推荐的 HBuilderX 工具创建项目,步骤清晰无门槛:

  1. 打开 HBuilderX,点击「文件 → 新建 → 项目」
  2. 选择「uni-app」项目类型,模板选择「默认模板」
  3. 项目名称填写blogs,选择本地保存路径,点击「创建」
项目初始目录结构说明
blogs ├── pages # 页面文件夹,所有业务页面都放在这里 ├── static # 静态资源,存放图片、字体、图标等文件 ├── App.vue # 全局配置文件,编写全局样式、应用生命周期 ├── main.js # 项目入口文件,挂载Vue实例、注册全局组件 ├── manifest.json # 应用配置文件,设置appid、权限、各平台专属配置 └── pages.json # 页面路由配置,管理页面路径、导航栏、底部Tabbar

创建完成后,点击工具栏「运行 → 运行到浏览器 → Chrome」,能正常打开默认首页即说明项目初始化成功。

2.2 Vue.js 简介及无数据视图

在对接接口数据前,我们先完成静态页面的搭建,也就是「无数据视图」。这里先梳理本项目用到的 Vue.js 核心特性:

  • 数据驱动视图:通过修改 data 中的数据,页面会自动更新,无需手动操作 DOM
  • 组件化开发:可将重复的 UI 模块封装成组件,实现多处复用、统一维护
  • 指令系统:通过v-forv-ifv-bindv-on等指令快速实现页面逻辑
2.2.1 splash 页面

Splash 页即启动欢迎页,是用户打开应用时的全屏过渡页,用于展示品牌标识、做初始化预加载。

  1. pages文件夹下新建splash文件夹,创建splash.vue文件

  2. pages.json中将该页面放在 pages 数组第一位,设为启动首页

  3. 页面实现代码:

<template> <view class="splash-container"> <image class="logo" src="/static/logo.png" mode="aspectFit"></image> <text class="app-name">Blogs 博客</text> </view> </template> <script> export default { onLoad() { // 延时1.5秒后跳转到博客首页 setTimeout(() => { uni.switchTab({ url: '/pages/blogs/blogs' }) }, 1500) } } </script> <style scoped> .splash-container { width: 100vw; height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: #ffffff; } .logo { width: 120rpx; height: 120rpx; margin-bottom: 30rpx; } .app-name { font-size: 36rpx; font-weight: bold; color: #333; } </style>
2.2.2 无数据视图

无数据视图也叫空状态页,在网络请求未返回、列表无数据时展示,避免页面空白降低用户体验。我们先在博客列表页实现基础空状态:

<template> <view class="blogs-page"> <!-- 有数据时展示列表 --> <view v-if="blogList.length > 0" class="blog-list"> <!-- 列表项后续接入数据后开发 --> </view> <!-- 无数据空状态 --> <view v-else class="empty-box"> <text class="empty-icon">📭</text> <text class="empty-text">暂无博客内容</text> </view> </view> </template> <script> export default { data() { return { blogList: [] // 初始为空数组,模拟无数据状态 } } } </script> <style scoped> .empty-box { padding-top: 200rpx; display: flex; flex-direction: column; align-items: center; color: #999; } .empty-icon { font-size: 80rpx; margin-bottom: 20rpx; } .empty-text { font-size: 28rpx; } </style>

2.3 blogs 列表视图显示及分页加载

完成静态页面后,我们接入真实接口数据,实现博客列表渲染和上拉加载更多的分页功能。

2.3.1 uni.request 网络请求封装

原生uni.request每次调用都需要编写完整 URL、重复处理加载状态与错误提示,开发效率低且维护成本高。我们对其进行二次封装,统一管理请求配置。

1.在项目根目录新建utils文件夹,创建request.js文件

2.封装代码如下:

// 接口基础地址,开发环境和生产环境可区分配置 const BASE_URL = 'https://你的接口域名/api' // 封装请求方法,返回Promise对象便于链式调用 export const request = (options) => { return new Promise((resolve, reject) => { // 请求前显示加载框 uni.showLoading({ title: '加载中...', mask: true }) uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', // 从缓存读取token,携带用户身份凭证 'Authorization': uni.getStorageSync('token') || '' }, success: (res) => { uni.hideLoading() // 统一处理业务状态码 if (res.data.code === 200) { resolve(res.data.data) } else { uni.showToast({ title: res.data.msg || '请求失败', icon: 'none' }) reject(res.data) } }, fail: (err) => { uni.hideLoading() uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' }) reject(err) } }) }) }

3.新建api/blog.js统一管理博客相关接口,实现接口与页面解耦:

import { request } from '@/utils/request.js' // 获取博客列表 export const getBlogList = (params) => { return request({ url: '/blog/list', method: 'GET', data: params }) }
2.3.2 CommonJS 及 ES6 模块化规范

上面的封装用到了模块化语法,这里区分前端两种主流的模块化规范,避免开发中混淆:

CommonJS 规范
  • 核心语法:使用require()导入模块,module.exports导出模块

  • 适用场景:Node.js 环境、早期前端工程化项目

  • 代码示例:

// 导出 module.exports = { request } // 导入 const { request } = require('../../utils/request.js')
ES6 模块化规范
  • 核心语法:使用import导入模块,export/export default导出模块

  • 适用场景:现代前端项目,Vue、React 等框架默认支持

  • 代码示例:即上文request.js的写法

UniApp 项目推荐使用 ES6 模块化规范,语法更灵活,且与 Vue 生态完美兼容。

列表分页加载实现

在博客列表页接入接口,实现下拉刷新、上拉加载更多的完整分页逻辑:

<script> import { getBlogList } from '@/api/blog.js' export default { data() { return { blogList: [], page: 1, pageSize: 10, hasMore: true // 标记是否还有下一页数据 } }, onLoad() { this.getList() }, // 下拉刷新生命周期 onPullDownRefresh() { this.page = 1 this.hasMore = true this.blogList = [] this.getList(() => { uni.stopPullDownRefresh() }) }, // 上拉触底生命周期 onReachBottom() { if (!this.hasMore) return this.page++ this.getList() }, methods: { getList(callback) { getBlogList({ page: this.page, pageSize: this.pageSize }).then(res => { // 拼接新数据到列表末尾 this.blogList = [...this.blogList, ...res.records] // 返回数据小于每页条数,说明没有下一页 if (res.records.length < this.pageSize) { this.hasMore = false } callback && callback() }).catch(() => { callback && callback() }) } } } </script>

2.4 iconfont 字体图标

项目中会用到大量图标,使用 Iconfont 字体图标比位图图片更节省体积、支持任意缩放不失真。接入步骤如下:

1.打开「阿里图标库」官网,搜索需要的图标(搜索、用户、发布、返回等),加入自己的图标项目

2.选择「Font class」使用方式,生成在线 CSS 链接

3.在项目App.vue的全局 style 中引入链接:

@import url('https://at.alicdn.com/t/c/font_xxxxxx_xxxxxx.css');

4.页面中直接通过类名使用图标:

<text class="iconfont icon-search"></text> <text class="iconfont icon-user"></text>

如果需要离线使用,可以将字体文件下载到本地static/font文件夹,修改 CSS 中的字体路径后本地引入即可。

2.5 uni 组件使用及 blogs 搜索功能

UniApp 提供了丰富的内置组件,我们可以基于原生组件快速实现博客搜索功能。

搜索功能实现

使用原生 input 组件实现搜索框,监听输入内容,调用搜索接口过滤列表数据:

<template> <view class="blogs-page"> <!-- 搜索框 --> <view class="search-box"> <input class="search-input" placeholder="搜索博客内容" v-model="keyword" confirm-type="search" @confirm="handleSearch" /> </view> <!-- 博客列表 --> <view v-if="blogList.length > 0" class="blog-list"> <view class="blog-item" v-for="item in blogList" :key="item.id"> <text class="title">{{ item.title }}</text> <text class="desc">{{ item.description }}</text> </view> </view> <!-- 无结果空状态 --> <view v-else class="empty-box"> <text class="empty-text">暂无相关博客</text> </view> </view> </template> <script> import { getBlogList } from '@/api/blog.js' export default { data() { return { keyword: '', blogList: [], page: 1, pageSize: 10 } }, methods: { handleSearch() { this.page = 1 this.blogList = [] this.getList() }, getList() { getBlogList({ page: this.page, pageSize: this.pageSize, keyword: this.keyword }).then(res => { this.blogList = [...this.blogList, ...res.records] }) } } } </script>
2.5.1 项目检查过程

开发到当前阶段,建议做一次全面的项目自查,避免后续功能堆积导致问题难定位:

  • 页面跳转检查:启动页能否正常跳转到首页,页面切换是否流畅无卡顿
  • 样式适配检查:切换不同尺寸的模拟器,查看是否有样式错乱、内容溢出
  • 网络请求检查:控制台查看接口是否正常返回、参数是否正确、错误提示是否正常触发
  • 边界场景检查:搜索空关键词、搜索无结果、分页到底部等异常场景是否正常处理
  • 代码报错检查:查看控制台是否有红色报错,及时修复语法错误、变量未定义等基础问题

2.6 使用缓存提升用户体验

移动端网络环境不稳定,合理使用本地缓存可以大幅减少白屏等待时间,提升用户体验。

缓存的常见使用场景
  • 列表数据缓存:进入页面先读取缓存数据渲染,同时请求最新数据,请求成功后更新页面与缓存
  • 用户信息缓存:登录后保存用户信息与 token,避免每次打开应用都需要重新登录
  • 搜索历史缓存:保存用户的搜索记录,下次进入页面直接展示历史关键词
列表缓存实现示例
methods: { getList() { // 1. 先读取本地缓存,快速渲染页面 const cacheData = uni.getStorageSync('blog_list_cache') if (cacheData) { this.blogList = cacheData } // 2. 请求最新接口数据 getBlogList({ page: 1, pageSize: 10 }).then(res => { this.blogList = res.records // 3. 更新本地缓存 uni.setStorageSync('blog_list_cache', res.records) }) } }
2.6.1 移动端请求类型

移动端开发中,常见的 HTTP 请求类型与使用场景如下:

请求方法核心用途是否适合缓存
GET获取数据(博客列表、详情、用户信息查询)✅ 适合做本地缓存
POST提交数据(登录、注册、发布博客)❌ 不建议缓存
PUT修改数据(编辑博客、更新用户信息)❌ 不建议缓存
DELETE删除数据(删除博客、删除评论)❌ 不建议缓存

我们的缓存策略仅针对 GET 查询类请求,对于数据提交类请求,必须实时与后端交互,不能使用本地缓存。

2.7 详细信息页面 & uni 路由

点击博客列表项进入详情页,这里涉及到 UniApp 的路由跳转能力。

路由跳转基础

UniApp 常用的路由 API 有三种,核心区别如下:

  • uni.navigateTo:保留当前页面,跳转到新页面,左上角自带返回按钮,适合详情页、二级页面
  • uni.redirectTo:关闭当前页面,跳转到新页面,无返回按钮,适合登录页、结果页
  • uni.switchTab:跳转到 TabBar 底部导航页面,只能用于底部标签页之间的切换
详情页实现

1.新建pages/blogDetail/blogDetail.vue,在pages.json中配置页面路径

2.列表页绑定点击事件,跳转时携带博客 ID:

goDetail(id) { uni.navigateTo({ url: `/pages/blogDetail/blogDetail?id=${id}` }) }

3.详情页接收参数,请求详情数据并渲染:

export default { data() { return { blogId: '', detail: {} } }, onLoad(options) { this.blogId = options.id this.getDetail() }, methods: { getDetail() { request({ url: `/blog/detail/${this.blogId}`, method: 'GET' }).then(res => { this.detail = res }) } } }
2.7.1 实现页面通知的两种方式

页面间传递信息、通知状态更新,最常用的有两种实现方式,覆盖不同场景:

方式一:路由传参(正向传值)

即上面详情页的实现方式,通过 URL 拼接参数传递数据。适合从上级页面向下级页面传递数据,语法简单直接,是最常用的传值方式。

方式二:全局事件总线(跨页面 / 反向传值)

使用 UniApp 内置的全局事件方法uni.$emituni.$on,可实现任意页面间的通信。比如发布博客成功后,通知列表页自动刷新数据:

// 发布页:发送全局事件 uni.$emit('blogPublished') // 列表页:监听全局事件 onLoad() { uni.$on('blogPublished', () => { this.page = 1 this.blogList = [] this.getList() }) }, onUnload() { // 页面销毁时移除监听,避免内存泄漏 uni.$off('blogPublished') }
2.7.2 复杂列表 & 评论显示

详情页底部通常包含评论列表,评论可能嵌套二级回复,属于典型的复杂嵌套列表。我们用双层v-for实现层级渲染:

<template> <view class="comment-section"> <view class="comment-title">全部评论</view> <!-- 一级主评论 --> <view class="comment-item" v-for="comment in commentList" :key="comment.id"> <view class="comment-header"> <image class="avatar" :src="comment.avatar"></image> <text class="username">{{ comment.username }}</text> </view> <text class="comment-content">{{ comment.content }}</text> <!-- 二级回复列表 --> <view class="reply-list" v-if="comment.replyList && comment.replyList.length > 0"> <view class="reply-item" v-for="reply in comment.replyList" :key="reply.id"> <text class="reply-name">{{ reply.replyName }}:</text> <text class="reply-content">{{ reply.content }}</text> </view> </view> </view> </view> </template>

2.8 自定义组件和用户登录

自定义组件封装

项目中很多重复的 UI 模块可以封装成自定义组件,提升代码复用性。以通用空状态组件为例:

1.新建components/empty-state/empty-state.vue

<template> <view class="empty-box"> <text class="empty-icon">{{ icon }}</text> <text class="empty-text">{{ text }}</text> </view> </template> <script> export default { props: { icon: { type: String, default: '📭' }, text: { type: String, default: '暂无数据' } } } </script>

2.页面中引入并使用:

<template> <empty-state text="暂无博客内容"></empty-state> </template> <script> import EmptyState from '@/components/empty-state/empty-state.vue' export default { components: { EmptyState } } </script>
用户登录功能

新建登录页面,实现表单验证与登录逻辑:

<template> <view class="login-page"> <input class="input-item" placeholder="请输入用户名" v-model="form.username" /> <input class="input-item" placeholder="请输入密码" password v-model="form.password" /> <button class="login-btn" @click="handleLogin">登录</button> <text class="go-register" @click="goRegister">没有账号?去注册</text> </view> </template> <script> import { request } from '@/utils/request.js' export default { data() { return { form: { username: '', password: '' } } }, methods: { handleLogin() { // 基础表单校验 if (!this.form.username || !this.form.password) { uni.showToast({ title: '请填写完整信息', icon: 'none' }) return } request({ url: '/user/login', method: 'POST', data: this.form }).then(res => { // 保存token和用户信息到本地缓存 uni.setStorageSync('token', res.token) uni.setStorageSync('userInfo', res.userInfo) uni.showToast({ title: '登录成功', icon: 'success' }) setTimeout(() => { uni.navigateBack() }, 1000) }) }, goRegister() { uni.navigateTo({ url: '/pages/register/register' }) } } } </script>

2.9 注册页面

注册页面逻辑与登录页类似,额外增加确认密码校验:

handleRegister() { const { username, password, confirmPassword } = this.form if (!username || !password || !confirmPassword) { uni.showToast({ title: '请填写完整信息', icon: 'none' }) return } if (password !== confirmPassword) { uni.showToast({ title: '两次密码不一致', icon: 'none' }) return } request({ url: '/user/register', method: 'POST', data: { username, password } }).then(res => { uni.showToast({ title: '注册成功', icon: 'success' }) setTimeout(() => { uni.navigateBack() }, 1000) }) }

2.10 个人中心页面

个人中心页展示用户信息与功能入口,从本地缓存读取用户数据渲染:

<template> <view class="profile-page"> <!-- 用户信息卡片 --> <view class="user-card"> <image class="avatar" :src="userInfo.avatar || '/static/default-avatar.png'"></image> <text class="username">{{ userInfo.username || '未登录' }}</text> </view> <!-- 功能菜单 --> <view class="menu-list"> <view class="menu-item" @click="goMyBlog"> <text class="iconfont icon-wode"></text> <text>我的博客</text> </view> <view class="menu-item" @click="goChart"> <text class="iconfont icon-tongji"></text> <text>数据统计</text> </view> <view class="menu-item" @click="logout" v-if="isLogin"> <text class="iconfont icon-tuichu"></text> <text>退出登录</text> </view> </view> </view> </template> <script> export default { data() { return { userInfo: {}, isLogin: false } }, onShow() { // 页面每次显示时刷新用户信息 const userInfo = uni.getStorageSync('userInfo') || {} this.userInfo = userInfo this.isLogin = !!uni.getStorageSync('token') }, methods: { logout() { uni.showModal({ title: '提示', content: '确定要退出登录吗?', success: (res) => { if (res.confirm) { uni.removeStorageSync('token') uni.removeStorageSync('userInfo') this.isLogin = false this.userInfo = {} } } }) } } } </script>

2.11 图表显示分析数据

数据统计页面我们使用 uCharts 图表库实现,直观展示博客发布量、阅读量等统计数据。

  1. 通过 HBuilderX 插件市场导入「qiun-data-charts」插件
  2. 页面中使用柱状图展示周度数据:
<template> <view class="chart-page"> <qiun-data-charts type="column" :opts="chartOpts" :chartData="chartData" /> </view> </template> <script> export default { data() { return { chartData: { categories: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], series: [ { name: '阅读量', data: [120, 200, 150, 80, 70, 110, 130] }, { name: '发布量', data: [5, 8, 3, 2, 4, 6, 7] } ] }, chartOpts: { color: ['#1890ff', '#52c41a'] } } } } </script>

2.12 添加博客

最后实现博客发布功能,用户可提交标题和正文内容发布新博客:

<template> <view class="add-blog-page"> <input class="title-input" placeholder="请输入博客标题" v-model="form.title" /> <textarea class="content-textarea" placeholder="请输入博客正文内容" v-model="form.content" maxlength="2000" ></textarea> <button class="submit-btn" @click="handleSubmit">发布博客</button> </view> </template> <script> import { request } from '@/utils/request.js' export default { data() { return { form: { title: '', content: '' } } }, methods: { handleSubmit() { if (!this.form.title.trim()) { uni.showToast({ title: '请输入博客标题', icon: 'none' }) return } if (!this.form.content.trim()) { uni.showToast({ title: '请输入博客内容', icon: 'none' }) return } request({ url: '/blog/add', method: 'POST', data: this.form }).then(res => { uni.showToast({ title: '发布成功', icon: 'success' }) // 发送全局事件,通知博客列表页刷新数据 uni.$emit('blogPublished') setTimeout(() => { uni.navigateBack() }, 1000) }) } } } </script>

3. 项目总结与拓展方向

核心内容总结

到这里,我们就完成了 Blogs 博客项目全部核心功能的开发,从项目初始化到各个业务模块落地,完整覆盖了 UniApp 开发的核心知识点:

  • 项目搭建规范与目录结构设计
  • Vue 基础语法与静态页面开发
  • 网络请求封装与模块化规范
  • 图标资源引入与原生组件使用
  • 本地缓存策略与用户体验优化
  • 路由跳转与页面间通信方案
  • 自定义组件封装思想
  • 登录注册与用户体系设计
  • 数据可视化与表单提交逻辑

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询