uniapp实战!一篇文章教你开发一个技术博客app!

0.前言

前面通过 2 篇文章带大家入门了 uniapp,想必大家对 uniapp 有了初步的了解。

1. 手把手教你用uniapp开发一个app(一)

css 复制代码
https://juejin.cn/post/7426916970661281802

2. 手把手教你用uniapp开发一个app(二)

css 复制代码
https://juejin.cn/post/7426914011558952986

本篇文章我直接通过实战的方式教会大家开发一个技术博客 app,后面会提供完整代码。

废话不多说,家人们,赶紧打开电脑,开干!

效果演示:

1.登录、列表页、详情页

2.新增博客、编辑博客、删除博客、个人中心、退出登录

本篇文章核心内容:

1. 新建基于 Vue3 的 uniapp 项目

注:请自行下载工具和配置环境,这里不再赘述。

1.开发工具:HBuilderX

HBuilderX 官网,下载之后直接解压打开使用

css 复制代码
https://www.dcloud.io/hbuilderx.html

2.开发环境:本地安装 Node

Node 官网:

css 复制代码
https://nodejs.cn/download/

Node 的安装和配置可以参考这篇文章:

css 复制代码
https://juejin.cn/post/7420978253492879410#heading-1

3.打开 HBuilder,新建项目

项目页面

4.新建页面

分别新建以下页面:

  • /login/login.vue 用户登录
  • /blog/list.vue 博客列表(首页)
  • /blog/add.vue 新增(编辑)博客
  • /blog/detail.vue 博客详情页
  • /user/user.vue 个人中心
  • /user/my-blog.vue 我发布的博客

2.封装网络请求

新建 api 文件夹,在 api 下面分别新建 request.js 和 http.js 文件。

2.1 request.js

request.js 文件主要包含 5 部分

  • 1.baseUrl
  • 2.uni.request 网络请求实例
  • 3.请求拦截
  • 4.响应拦截
  • 5.异常处理

2.2 http.js

http.js 文件主要是引入 request.js 实例,然后封装 GET、POST、PUT、DELETE 方法

2.3 封装请求的 API

其中 user.js 和 blog.js 主要用来封装请求后端的 API

2.4 第三方的网络请求库

除了自己封装 uniapp 的网络请求,我们还可以使用第三方的网络请求库

1.去 uniapp 插件市场安装第三方的 uniapp 网络请求库,注意有些是收费的

css 复制代码
https://ext.dcloud.net.cn/

2.去 npm 仓库查找

css 复制代码
https://www.npmjs.com

3.去某SDN、某客园、某度、某AI平台查找别人推荐的第三方库,然后安装配置使用

3.第三方前端组件库

uniapp 内置组件库和扩展组件库其实已经满足我们大部分的需求,但是仍然有些不足之处,所以大部分人都会选择第三方的组件库。

uniapp 官方插件市场推荐的前端组件库是 uView

但是美中不足的是它不兼容 Vue3

这里我选择另外一个组件库:uv-ui

uv-ui 大部分组件基于 uView2.x,改进之后支持兼容 Vue3

uv-ui 官网:

css 复制代码
https://www.uvui.cn/

3.1 安装配置 uv-ui

方式 1 :去 HbuilderX 的插件市场下载安装

css 复制代码
https://ext.dcloud.net.cn/plugin?name=uv-ui

提醒:下载安装之前需要微信看广告,这里推荐方式 2 安装。

方式 2 :npm 安装

css 复制代码
npm i @climblee/uv-ui

然后在 pages.json 文件里面配置 easycom

css 复制代码
"easycom": {
		"autoscan": true,
		"custom": {
			"^uv-(.*)": "@climblee/uv-ui/components/uv-$1/uv-$1.vue"
		}
	},

安装配置之后我们就可以使用 uv-ui 的组件库了,uv-ui 所有组件标签都是以 uv 开头的,例如:

4.自定义底部导航栏

前面基础篇讲过,我们需要在 pages.json 的 tabBar 上面配置 app 的底部导航栏。

核心配置:

  • text:导航栏名称
  • pagePath::导航栏对应的页面路径
  • iconPath:导航栏未选中时的图标
  • selectedIconPath:导航栏被选中时的图标

但是这种不能满足我们的需求,我们需要自定义底部导航栏。

注:如果自定义底部导航栏,需要删掉 pages.json 里面 tabBar 的配置,否则无效。

4.1 自定义底部导航栏组件

在 components 文件下面新建同名目录的 tab-bar.vue 文件

底部导航栏我们用到了 uv-ui 的 Tabbar 组件

html 复制代码
<uv-tabbar :value="itemValue" activeColor="#000000">
			<uv-tabbar-item text="首页" @click="toIndex" icon="home"></uv-tabbar-item>
			<uv-tabbar-item iconSize="40" @click="toAddBlog" icon="plus-circle"></uv-tabbar-item>
			<uv-tabbar-item text="我的" @click="toUserCenter" icon="account"></uv-tabbar-item>
</uv-tabbar>

这个组件的核心配置:

  • value:当前匹配项的 index,默认从 0 开始
  • text:导航栏名称
  • icon:导航栏图标
  • activeColor:选中导航栏标签时的颜色,这里设置为 black,纯黑色

4.1.1 接收 index 参数

css 复制代码
// props 接收父组件传递的参数
const props = defineProps(["itemValue"])

4.1.2 点击首页跳转页面

js 复制代码
// 首页博客列表页面
	const toIndex = () => {
		uni.navigateTo({
			url: "/pages/blog/list"
		})
	}

4.1.3 封装 Hooks

首页博客列表页面不需要登录就能访问,但是新增博客和个人中心页面需要验证登录状态。

因为进入到这两个页面的操作逻辑是一样的

    1. 验证是否登录,未登录跳转登录页面
    1. 登录验证通过,跳转到指定页面

所以这里我将校验登录并跳转指定页面的功能封装成一个 Hooks

新建 hooks 文件夹,新建 useCheckLoginAndNavigate.js 文件

如果不知道 Hooks 是什么东西,可以看我写的这篇文章:

css 复制代码
https://juejin.cn/post/7418397103909470248

然后在自定义的导航栏组件里面引入并使用 Hooks

4.1.4 进入新增博客页面

这里使用到了钩子函数 checkLogin

js 复制代码
// 进入新增博客页面
const toAddBlog = () => {
    checkLogin("/pages/blog/add");
}

4.1.5 进入个人中心页面

这里使用到了钩子函数 checkLogin

js 复制代码
// 进入个人中心页面
const toUserCenter = () => {
    checkLogin("/pages/user/user");
}

4.1.6 自定义导航栏完整代码

所有的功能代码我都添加了注释,方便大家学习

css 复制代码
<template>
	<view>
		<!-- 底部导航栏 -->
		<uv-tabbar :value="itemValue" activeColor="black">
			<uv-tabbar-item text="首页" @click="toIndex" icon="home"></uv-tabbar-item>
			<uv-tabbar-item iconSize="40" @click="toAddBlog" icon="plus-circle"></uv-tabbar-item>
			<uv-tabbar-item text="我的" @click="toUserCenter" icon="account"></uv-tabbar-item>
		</uv-tabbar>
	</view>
</template>

<script setup>
	// 校验登录并跳转的 hooks
	import {
		useCheckLoginAndNavigate
	} from '@/hooks/useCheckLoginAndNavigate';
	const {
		checkLogin
	} = useCheckLoginAndNavigate();
	// 引入 vue 相关库
	import {
		ref
	} from 'vue';
	// props 接收父组件传递的参数
	const props = defineProps(["itemValue"])
	// 首页博客列表页面
	const toIndex = () => {
		uni.navigateTo({
			url: "/pages/blog/list"
		})
	}
	// 进入新增博客页面
	const toAddBlog = () => {
		checkLogin("/pages/blog/add");
	}
	// 进入个人中心页面
	const toUserCenter = () => {
		checkLogin("/pages/user/user");
	}
</script>

<style lang="scss" scoped>
	::v-deep .uvicon-plus-circle {
		color: #5c89fe !important;
	}
</style>

4.2 引入底部导航栏

博客列表(首页)引入自定义底部导航栏组件

个人中心引入自定义底部导航栏组件

效果:

5.用户登录功能

登录页面用到了 uv-ui 的 uv-form 组件

css 复制代码
<uv-form class="login_form" labelPosition="left" :model="loginForm" :rules="loginPasswordRule"
    ref="loginFormRef">
    <uv-form-item :borderBottom="false" prop="username" borderBottom>
        <uv-input placeholder="请输入账号" clearable v-model="loginForm.username" prefixIcon="account"
            prefixIconStyle="color: #909399"></uv-input>
    </uv-form-item>
    <uv-form-item :borderBottom="false" prop="password" borderBottom>
        <uv-input placeholder="请输入密码" v-model="loginForm.password" type="password" :password="true"
            prefixIcon="lock" clearable prefixIconStyle="color: #909399"></uv-input>
    </uv-form-item>
    <uv-button type="primary" text="登录" customStyle="margin-top: 10px;background-color:#5c89fe;border:none;"
        @click="login">登录</uv-button>
</uv-form>

还用到了 uv-ui 的消息提示组件:uv-toast

css 复制代码
<uv-toast ref="toast"></uv-toast>

登录功能比较简单:

  • 引入 form 组件
  • 绑定 from 对象
  • 绑定 rules 校验规则
  • 登录函数

登录成功之后缓存用户信息 和 token,登录失败就提示错误信息

登录页面完整代码:

xml 复制代码
<template>
	<view>

		<uv-image class="brand_logo" width="80px" height="80px" :src="logoSrc"></uv-image>
		<p style="text-align: center;color: #82848a;">知否技术博客</p>
		<uv-form class="login_form" labelPosition="left" :model="loginForm" :rules="loginPasswordRule"
			ref="loginFormRef">
			<uv-form-item :borderBottom="false" prop="username" borderBottom>
				<uv-input placeholder="请输入账号" clearable v-model="loginForm.username" prefixIcon="account"
					prefixIconStyle="color: #909399"></uv-input>
			</uv-form-item>
			<uv-form-item :borderBottom="false" prop="password" borderBottom>
				<uv-input placeholder="请输入密码" v-model="loginForm.password" type="password" :password="true"
					prefixIcon="lock" clearable prefixIconStyle="color: #909399"></uv-input>
			</uv-form-item>
			<uv-button type="primary" text="登录" customStyle="margin-top: 10px;background-color:#5c89fe;border:none;"
				@click="login">登录</uv-button>
		</uv-form>
		<uv-toast ref="toast"></uv-toast>
		<view class="footer">
			<p>版权所有 © 知否技术 浙ICP备xxxx号</p>
		</view>
	</view>
</template>

<script setup>
	// 引入登录相关的 API
	import userApi from "@/api/user/user.js"
	// 引入图标
	import logoSrc from "@/static/images/zhi.png"
	// 引入 Vue 相关库
	import {
		ref,
		reactive
	} from 'vue';
	// toast 组件
	const toast = ref(null);
	// 登录 form
	const loginForm = reactive({
		username: "",
		password: ""
	})
	// 校验规则
	const loginPasswordRule = reactive({
		username: [{
			type: 'string',
			required: true,
			message: "账号不能为空",
			trigger: "blur"
		}],
		password: [{
			type: 'string',
			required: true,
			message: "密码不能为空",
			trigger: "blur"
		}]
	});
	const loginFormRef = ref();
	// 用户登录
	const login = () => {
		loginFormRef.value.validate().then(res => {
			userApi.login(loginForm).then((res) => {
				if (res.data.code === 200) {
					// 缓存用户信息
					uni.setStorageSync("userInfo", JSON.stringify(res.data.data
						.userInfo));
					// 缓存 token信息
					uni.setStorageSync("token", res.data.data.token || "123456");
					// 跳转到首页
					uni.navigateTo({
						url: `/pages/blog/list`
					})
				} else {
					toast.value.show({
						message: res.data.message,
						type: "error",
						position: "center"
					})
				}
			}).catch(error => {
				toast.value.show({
					message: error.errMsg,
					type: "error",
					position: "center"
				})
			})
		}).catch(errors => {
			toast.value.show({
				message: "数据校验失败",
				type: "error",
				position: "center"
			})
		})
	}
</script>

<style lang="scss" scoped>
	.brand_logo {
		margin: 200rpx auto 30rpx;
	}

	.login_form {
		padding: 20rpx 50rpx;
	}
	.footer {
		font-size: 13px;
		text-align: center;
		color: #b7b7b8;
	}
</style>

6.博客列表页面

6.1 自定义头部导航栏

要自定义头部导航栏,首先 pages.json 文件 navigationStyle 属性需要设置为自定义 "custom"。

头部导航栏组件用到了 uv-ui 的 uv-navbar 组件,这里有两点需要注意:

css 复制代码
<view class="navbar">
    <uv-navbar leftIcon="" :fixed="false" bgColor="#f8f8f8" title="知否技术博客"></uv-navbar>
</view>
    1. fixed 默认是 true,会遮挡下面的内容,所以这里设置为 false。
    1. leftIcon 表示左侧图标,因为是首页,所以这里不显示图标

6.2 标签选项卡

标签选项卡用到了 uv-ui 的 uv-tabs 组件

css 复制代码
<view>
<!-- Tabs 标签选项卡 -->
<uv-tabs @change="changeTab" class="uv-border-bottom" :scrollable="false" :list="tabList"
    @click="click"></uv-tabs>
</view>

这个组件的核心内容:

    1. list 绑定标签列表数据
    1. @change 绑定切换标签函数
    1. scrollable 设置左右是否滚动

6.3 封装列表内容

6.3.1 获取后台数据

1.调用后台接口

因为列表页面获取的数据是分页的,所以查询参数要包含当前页和每页显示的数据个数:

css 复制代码
// 查询 form
  const search = reactive({
      current: 1,
      size: 10,
      type: "java",
  })

2.查询博客分页数据

css 复制代码
// 博客数据
const blogDataList = ref([]);
//  0 - 默认,1 - 正在加载数据中,2 - 真的没有数据了
const loading = ref(0);
// 获取博客分页数据
const getDataList = async () => {
    const {
        data
    } = await blogApi.getBlogList(search);
    if (data.data.records.length == 0) {
        loading.value = 2;
    }
    blogDataList.value = [...blogDataList.value, ...data.data.records]
    // 停止下拉刷新
    uni.stopPullDownRefresh();
}

这里我们通过 loading 值来显示页面的内容:0-默认,1-加载中,2-没有其他数据了

我们在滑动手机页面的时候会有一个上拉触底的交互,所以会继续获取下一页的数据,这时候显示的数据应该是前面数据+最新获取的数据:

css 复制代码
blogDataList.value = [...blogDataList.value, ...data.data.records]

6.3.2 设计页面布局

我们想要的效果是头部、底部固定,中间滚动。

头部的 tabs 标签页和底部的导航栏不用管,只需要中间滚动即可。

让中间的内容滚动,我们想到了 uniapp 内置的组件 scroll-view。

所以列表页面的内容就是 scroll-view 包含了卡片。

但是 uv-ui 没有 card 组件,这里选择使用 uniapp 的扩展组件 uni-card,需要下载安装:

安装之后的 uni-card 组件在 uni_modules 这个文件夹下

我们先设计卡面里面的内容:

css 复制代码
<uni-card class="blog-content" v-for="(item,index) in blogDataList" :key="index"
    @click="toBlogDetail(item.id)" :title="item.title" :is-shadow="false"
    :extra="timeFormat(item.createTime)">
    <uv-row customStyle="margin-bottom: 5px">
        <uv-col span="3">
            <uv-image src="https://picsum.photos/seed/picsum/300/300" width="50px"
                height="50px"></uv-image>
        </uv-col>
        <uv-col span="9">
            <text class="uv-line-2">{{item.content}}</text>
        </uv-col>
    </uv-row>
</uni-card>

1.通过 v-for 遍历博客列表数据

2.使用 uv-row 和 uv-col 进行布局,其中左侧是图片,右侧是内容。

3.uv-image 用来显示图片,src 是图片地址,这里给了一个随机显示图片的网址

css 复制代码
<uv-image src="https://picsum.photos/seed/picsum/300/300" width="50px"
									height="50px"></uv-image>

4.text 组件显示博客内容,uv-line-2 表示超过 2 行就显示省略号,隐藏其他内容。

css 复制代码
<text class="uv-line-2">{{item.content}}</text>

5.timeFormat 使用了 uv-ui 的格式化时间的js库,需要自行导入

css 复制代码
import {
		timeFormat
	} from '@climblee/uv-ui/libs/function/index.js';

接下来设计整个页面的布局

1.scroll-view 设置 scroll-y 为 true

2.整个页面的布局是 flex 布局

3.flex-grow 定义子容器的瓜分剩余空间的比例为 1

4.scroll-content 也设置为弹性布局,主轴是侧轴

css 复制代码
.content {
    display: flex;
    flex-direction: column;
    height: 100vh;
}
.list-wrap {
    flex-grow: 1;
    position: relative;
}
.list-scroll-view {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
}
.scroll-content {
    display: flex;
    flex-direction: column;
    margin-left: 1vw;
    margin-right: 1vw;
}

6.4 下拉刷新

1.pages.json 开启下拉刷新

2.下拉刷新

首先导入 uniapp 的下拉刷新库:onPullDownRefresh

css 复制代码
import {
		onLoad,
		onPullDownRefresh,
		onReachBottom
	} from "@dcloudio/uni-app"

绑定下拉刷新方法:

  • 当前页初始化为 1
  • 初始化 loading 的值为 0
  • 初始化 blogDataList 数组的值
  • 查询列表数据
js 复制代码
// 下拉刷新方法
  onPullDownRefresh(() => {
      search.current = 1;
      loading.value = 0;
      blogDataList.value = [];
      getDataList();
  })

6.5 上拉触底

首先导入 uniapp 的上拉触底库:onReachBottom

css 复制代码
import {
		onLoad,
		onPullDownRefresh,
		onReachBottom
	} from "@dcloudio/uni-app"

绑定上拉触底函数:

css 复制代码
//上拉触底	
	onReachBottom(() => {
		if (loading.value == 2) {
			return;
		}
		search.current++;
		loading.value = 1;
		getDataList();
	})

但是!但是!但是!

因为列表页面我们用的 scroll-view 组件,所以 onReachBottom 这个函数不会触发,需要单独绑定 @scrolltolower="scrolltolower"

上拉触底需要获取下一页数据

css 复制代码
// 上拉触底
	const scrolltolower = () => {
		if (loading.value == 2) {
			return;
		}
		search.current++;
		loading.value = 1;
		getDataList();
	}

6.6 封装加载列表的样式

再次获取下一页数据,显示无数据

数据为空显示

css 复制代码
<!-- 数据加载 -->
  <view class="loading">
      <view v-if="loading==1">数据加载中...</view>
      <view v-if="loading==2 && blogDataList.length>0 ">亲,暂无数据...</view>
      <view v-if="loading==2 && blogDataList.length==0 ">
          <uv-image style="margin:30rpx auto ;" mode="widthFix" width="300rpx" height="300rpx"
              src="/static/images/empty.png"></uv-image>
      </view>
  </view>

6.7 跳转博客详情页

给 uni-card 绑定点击事件:

js 复制代码
// 进入到博客详情页面
  const toBlogDetail = (id) => {
      uni.navigateTo({
          url: "/pages/blog/detail?blogId=" + id
      })
  }

7.博客详情页

7.1 页面布局

博客详情页布局很简单

  • 通过 uv-row 和 uv-col 布局
  • uv-image 组件加载图片
  • uv-text 组件显示文本信息
  • timeFormat 格式化工具库格式化时间
css 复制代码
<view class="centent">
    <!-- 博客详情 -->
    <view class="blog_title">
        <h3>{{blogInfo.title}}</h3>
    </view>
    <view>
        <uv-row customStyle="padding: 10px">
            <uv-col span="2">
                <uv-image src="https://picsum.photos/seed/picsum/300/300" width="50px" height="50px"></uv-image>
            </uv-col>
            <uv-col span="10">
                <p style="margin-bottom:5px">作者:{{blogInfo.username}}</p>
                <uv-text type="info" :text="timeFormat(blogInfo.createTime,'yyyy-mm-dd hh:MM:ss')"></uv-text>
            </uv-col>
        </uv-row>
    </view>
    <uv-line></uv-line>
    <view style="padding: 10px">
        <uv-parse :content="blogInfo.content"></uv-parse>
    </view>
</view>

7.2 获取后台详情页数据

1.在 onLoad 生命周期函数里面获取跳转路径携带的参数 id,然后调用获取详情的函数

css 复制代码
onLoad((option) => {
		blogInfo.id = option.blogId;
		getBlogDetail();
	})

2.获取后台详情页数据

css 复制代码
// blogInfo form
	const blogInfo = reactive({
		id: "",
		title: "",
		type: "",
		content: "",
		createTime: ""
	})
	// 获取博客详情数据
	const getBlogDetail = async () => {
		const {
			data
		} = await blogApi.getBlogInfo(blogInfo.id);
		Object.assign(blogInfo, data.data);
	}

8.新增博客

为通用底部导航栏组件绑定进入到新增博客页面的函数

css 复制代码
// 进入新增博客页面
	const toAddBlog = () => {
		checkLogin("/pages/blog/add");
	}

8.1 页面布局

1.头部导航栏

头部导航栏是自定义的, pages.json 里面 navigationStyle 属性需要设置为 custom

css 复制代码
<view>
			<uv-navbar :fixed="false" @leftClick="leftClick" bgColor="#f8f8f8" :title="title"></uv-navbar>
		</view>

title 是动态变化的,默认是"新增博客"

css 复制代码
	// 当前页面默认导航栏标题
	const title = ref("新增博客");

2.form 表单

form 表单 用了 uv-form 组件

css 复制代码
<uv-form class="blog_form" labelPosition="left" :model="blogForm" :rules="blogFormRule" ref="blogFormRef">
    <uv-form-item label="标题:" prop="title">
        <uv-input placeholder="请输入标题" clearable v-model="blogForm.title"></uv-input>
    </uv-form-item>
    <uv-form-item label="类别:" prop="type" @click="showTypes">
        <uv-input border="surround" v-model="blogForm.type" suffixIcon="arrow-right" disabled
            disabledColor="#ffffff" placeholder="请选择类别">
        </uv-input>
    </uv-form-item>
    <uv-form-item label="内容:" prop="content">
        <uv-textarea autoHeight v-model="blogForm.content" placeholder="请输入内容"></uv-textarea>
    </uv-form-item>
    <uv-button type="primary" text="提交" customStyle="margin-top: 10px;background-color:#5c89fe;border:none;"
        @click="saveBlog"></uv-button>
</uv-form>

绑定的 form 数据

js 复制代码
// 保存博客的 form
	const blogForm = reactive({
		id: "",
		title: "",
		type: "",
		userId: "",
		content: ""
	})

2.选择类别

首先为选择类别的 uv-form-item 绑定点击方法

选择类别用了 uv-ui 的 uv-action-sheet 组件

xml 复制代码
<!-- 选择类别 -->
		<uv-action-sheet ref="blogTypeRef" :actions="typeList" title="请选择技术类别" @select="selectType">
js 复制代码
// 博客类别 ref
	const blogTypeRef = ref(null);
	// 打开 uv-action-sheet
	const showTypes = () => {
		blogTypeRef.value.open();
	}

actions 绑定类别数组数据

js 复制代码
// 博客类别
	const typeList = ref([{
		name: 'java'
	}, {
		name: 'vue'
	}, {
		name: 'react'
	}, {
		name: 'uniapp'
	}, {
		name: 'electron'
	}, {
		name: 'js'
	}])

@select 绑定选择类别方法

js 复制代码
// 选择类别
	const selectType = (e) => {
		blogForm.type = e.name;
	}

8.2 保存博客数据

保存成功跳转到博客列表页面

js 复制代码
	const saveBlog = () => {
		blogFormRef.value.validate().then(async () => {
			const {
				data
			} = await blogApi.saveBlog(blogForm);
			if (data.code == 200) {
				uni.showToast({
					title: '保存成功!',
				});
				uni.navigateTo({
					url: "/pages/blog/list"
				})
			} else {
				uni.showToast({
					title: data.message,
				});
			}
		}).catch(errors => {
			toast.value.show({
				message: "数据校验失败",
				type: "error",
				position: "center"
			})
		})
	}

9.个人中心

为通用底部导航栏组件绑定进入到个人中心的函数

css 复制代码
// 进入个人中心页面
	const toUserCenter = () => {
		checkLogin("/pages/user/user");
	}

9.1 页面布局

css 复制代码
	<view>
		<uv-navbar leftIcon="" :fixed="false" bgColor="#f8f8f8" title="个人中心"></uv-navbar>
		<view style="padding: 10px;">
			<uv-row>
				<uv-col span="2">
					<uv-image src="/static/images/zhi.png" width="50px" height="50px"></uv-image>
				</uv-col>
				<uv-col span="10">
					<p style="margin-bottom:5px">{{userInfo.name}}</p>
					<uv-text type="info" customStyle="font-size:12px" :text="'编号:'+userInfo.code"></uv-text>
				</uv-col>
			</uv-row>
		</view>
		<view>
			<uv-cell-group>
				<uv-cell title="我的博客" @click="myBlog"><template v-slot:right-icon>
						<uv-icon size="30rpx" name="arrow-right"></uv-icon>
					</template></uv-cell>
				<uv-cell title="退出登录" @click="logout"><template v-slot:right-icon>
						<uv-icon size="30rpx" name="arrow-right"></uv-icon>
					</template></uv-cell>
			</uv-cell-group>
		</view>
		<!-- 底部导航栏 -->
		<tab-bar :itemValue="2"></tab-bar>
	</view>

1.头部导航栏

头部导航栏也是自定义的:

css 复制代码
<uv-navbar leftIcon="" :fixed="false" bgColor="#f8f8f8" title="个人中心"></uv-navbar>

2.个人中心操作栏

显示个人信息的布局主要是用运用 uv-row、uv-col 和 uv-image,下面的操作栏用了 uv-cell-group 组件。

css 复制代码
<uv-cell-group>
    <uv-cell title="我的博客" @click="myBlog"><template v-slot:right-icon>
            <uv-icon size="30rpx" name="arrow-right"></uv-icon>
        </template></uv-cell>
    <uv-cell title="退出登录" @click="logout"><template v-slot:right-icon>
            <uv-icon size="30rpx" name="arrow-right"></uv-icon>
        </template></uv-cell>
</uv-cell-group>

3.获取个人数据

从缓存中获取用户信息

js 复制代码
// 用户信息
let userInfo = reactive({
    name: "",
    code: ""
})
// 页面初始化生命周期函数
onLoad(() => {
    userInfo = JSON.parse(uni.getStorageSync("userInfo"));
})

10.我发布的博客

为"我的博客"绑定点击事件:

css 复制代码
// 我的博客
const myBlog = () => {
    uni.redirectTo({
        url: "/pages/user/my-blog"
    })
}

10.1 列表

新建 my-blog.vue 文件

我发布的博客列表页面和首页列表一摸一样,主要是查询后台参数时多传递了用户的 id

js 复制代码
// 页面初始化生命周期函数
	onLoad(() => {
		const userInfo = JSON.parse(uni.getStorageSync("userInfo"));
		search.userId = userInfo.id;
		getDataList();
	})

当然实际项目中一般是后台从缓存中获取用户信息,这里只是告诉大家怎么获取"我发布的博客"。

10.2 编辑博客

点击编辑按钮跳转到编辑博客页面,并传递 id

js 复制代码
// 编辑博客
	const toUpdate = (id) => {
		uni.navigateTo({
			url: "/pages/blog/add?id=" + id
		})
	}

编辑博客和新增博客共用 add.vue 页面,只不过是编辑的时候根据 id 先获取博客详情数据,头部导航栏的标题改为"编辑博客"。

js 复制代码
// 页面初始化生命周期函数
	onLoad((option) => {
		// 获取跳转页面携带的参数
		if (option.id) {
			title.value = "编辑博客";
			blogForm.id = option.id;
			// 获取博客详情信息
			getBlogDetail();
		}
		// 获取缓存中的用户数据
		const userInfo = JSON.parse(uni.getStorageSync("userInfo"));
		blogForm.userId = userInfo.id;
	})

获取博客详情数据

js 复制代码
	// 获取博客详情
	const getBlogDetail = async () => {
		const {
			data
		} = await blogApi.getBlogInfo(blogForm.id);
		Object.assign(blogForm, data.data);
	}

编辑博客和新增博客共用一个后台接口。

10.3 删除博客

删除博客用到了 uv-ui 的 uv-modal 组件,@confirm 绑定确认事件,@cancel 绑定取消事件

css 复制代码
	<uv-modal ref="deleteModal" :showCancelButton="true" title="确认删除该博客吗?" @cancel="cancelDelete"
				@confirm="deleteBlog"></uv-modal>

1.点击删除按钮,弹出模态框

js 复制代码
const deleteModal = ref(null);
	// 打开删除博客弹框
	const blogId = ref("");
	const openDeleteModal = (id) => {
		deleteModal.value.open();
		blogId.value = id;
	}

2.确认事件

点击确认,删除数据

js 复制代码
// 删除博客操作
const deleteBlog = async () => {
    const {
        data
    } = await blogApi.delBlog(blogId.value);
    console.log("data:", data)
    if (data.code === 200) {
        uni.showToast({
            title: "删除成功"
        })
        loading.value = 0;
        blogDataList.value = [];
        getDataList();
    } else {
        uni.showToast({
            title: data.message
        })
    }
}

3.取消事件

点击取消,调用取消删除函数。

js 复制代码
// 取消删除
  const cancelDelete = () => {
      deleteModal.value.close();
      blogId.value = "";
  }

11.退出功能

在个人中心,为"退出登录"绑定事件

退出登录的核心就是清除缓存、跳转到登录页面。

js 复制代码
// 退出
const logout = () => {
    // 清除缓存
    uni.clearStorageSync();
    uni.removeStorageSync("userInfo")
    // 退出
    uni.reLaunch({
        url: "/pages/login/login"
    })
}

12.打包安装app

注:这里只以安卓手机为例

12.1 注册账号

首先去 DCloud 的开发者中心注册账号

css 复制代码
https://dev.dcloud.net.cn/

uni-app 打包方式目前有两种,云打包和本地打包,

所谓的云打包就是将我们的代码发送到 uniapp 的云端服务器环境进行打包,但是一天只能免费 5 次,第 6 次收费,而且需要排队。

因为云打包比较简单,本地打包比较麻烦,这里先介绍云打包,后面有时间会介绍本地打包。

打包的应用可以在应用管理看到:

12.2 基础配置

点击 manifest.json,配置应用标识、应用名称、描述和版本号。

12.3 图标配置

配置 app 的图标

12.4 云打包

点击发行,选择云打包

使用云端证书,其他的默认,点击打包

需要等待

打包后的 app 文件在 zhifou-uniapp\unpackage\release\apk 这个路径下,然后通过微信等工具保存到手机本地,安装即可

注意!安装之前切记先卸载本地"国家反诈中心"app,不然帽子叔叔会给你打电话,本人亲测。

授权本次安装

13.完整代码

Gitee

css 复制代码
https://gitee.com/zhifou-tech/zhifou-uniapp

网盘

css 复制代码
链接: https://pan.baidu.com/s/1FdaxngRWe4ucR-7ohOgFVw?pwd=1234 
提取码: 1234 

拿到代码之后直接用 HBuilderX 打开运行即可

无人扶我青云志,我自踏雪至山巅。若是命中无此运,亦可孤身登昆仑。

相关推荐
秋田君12 分钟前
uniapp跨域问题解决方案
uni-app
T^T尚21 分钟前
uniapp H5上传图片前压缩
前端·javascript·uni-app
出逃日志43 分钟前
JS的DOM操作和事件监听综合练习 (具备三种功能的轮播图案例)
开发语言·前端·javascript
XIE3921 小时前
如何开发一个脚手架
前端·javascript·git·npm·node.js·github
山猪打不过家猪1 小时前
React(五)——useContecxt/Reducer/useCallback/useRef/React.memo/useMemo
前端·javascript·react.js
前端青山1 小时前
React事件处理机制详解
开发语言·前端·javascript·react.js
科技D人生1 小时前
Vue.js 学习总结(14)—— Vue3 为什么推荐使用 ref 而不是 reactive
前端·vue.js·vue ref·vue ref 响应式·vue reactive
对卦卦上心1 小时前
React-useEffect的使用
前端·javascript·react.js
练习两年半的工程师1 小时前
React的基本知识:事件监听器、Props和State的区分、改变state的方法、使用回调函数改变state、使用三元运算符改变state
前端·javascript·react.js
啵咿傲1 小时前
在React中实践一些软件设计思想 ✅
前端·react.js·前端框架