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
首页博客列表页面不需要登录就能访问,但是新增博客和个人中心页面需要验证登录状态。
因为进入到这两个页面的操作逻辑是一样的
-
- 验证是否登录,未登录跳转登录页面
-
- 登录验证通过,跳转到指定页面
所以这里我将校验登录并跳转指定页面的功能封装成一个 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>
-
- fixed 默认是 true,会遮挡下面的内容,所以这里设置为 false。
-
- 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>
这个组件的核心内容:
-
- list 绑定标签列表数据
-
- @change 绑定切换标签函数
-
- 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 打开运行即可
无人扶我青云志,我自踏雪至山巅。若是命中无此运,亦可孤身登昆仑。