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 打开运行即可

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

相关推荐
腾讯TNTWeb前端团队3 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰6 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪6 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪7 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy7 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom8 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom8 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom8 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom8 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom8 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试