uni-app 结合云函数开发小程序博客(三):接入云函数,实现完善的列表刷新机制_一只图雀的博客-程序员秘密

在这里插入图片描述

我们研发开源了一款基于 Git 进行技术实战教程写作的工具,我们图雀社区的所有教程都是用这款工具写作而成,欢迎 Star

如果你想快速了解如何使用,欢迎阅读我们的 教程文档哦

本文由图雀社区认证作者 测不准 使用 Tuture 写作工具写作而成,图雀社区将连载其 uni-app 结合云函数开发小程序博客系列,感谢作者的优质输出,让我们的技术世界变得更加美好

因为项目做的是博客demo, 首页进来想给人直观的就能看到文章,看到分类。所以想的一个是可以左右滑动,切换分类,一个是页面以列表形式,直接 list 渲染。类似掘金的样式:

  1. 页面的左右滑动,并自带过渡效果,直接就可以使用自带的swiper组件;
  2. 顶部的分类导航其实也是跟着可以左右滑动,并且跟随swiper 页面同步切换,选择也是小程序组件scroll-view,设置为左右滑动;
  3. 上拉加载更多:小程序有自带的生命周期 onReachBottom, 默认距离底部50px距离,想要修改可以在页面的style 中设置 onReachBottomDistance 字段
  4. 下拉刷新:小程序页面生命周期 onPullDownRefresh,同时要在页面的style重配置
    enablePullDownRefresh:true开启下拉刷新;掘金的下拉刷新是安卓app的下拉样式,当你用uniapp开发应用,真机运行是可以看到如下结果。

小程序自带的下拉样式如下(原生导航条):

使用自定义导航条下拉样式如下:它会从最顶部开始出现下拉样式

很明显上面的结果不是我们想要的,常用的方式是自定义导航条下面的内容区使用scroll-view 组件,通过scroll-view监听到达顶部和到达底部,继而出发下拉和上拉。为了避免重复造轮子和瞻仰大佬的代码,咱们选择到插件市场逛一逛:最终我选择了如下插件

引入插件进行布局

  1. 下载插件,把如下两个文件夹复制到自己的项目中,我放到了(/colorui/components/),scroll有空数据时的图片记得一并引入。

大家可以自己运行下载的zip安装包,项目直接可以跑通。咱们这里不做演示,直接引入插件中的代码放入首页: (代码参考插件中的/pages/swipe-list/index.vue

以下代码在 /pages/home/home.vue 中
...
<view class="top-wrap"><tab id="category" :tab-data="categoryMenu" :cur-index="categoryCur" :size="80" :scroll="true" @change="toggleCategory"></tab></view>
// 这里 swiper使用的是animationfinish,当滑动完成后改变,也可以使用 change事件
<swiper :current="categoryCur" :duration="duration" @animationfinish="swipeChange">
	<swiper-item v-for="(item, index) in categoryData" :key="index">
		<scroll :requesting="item.requesting" :end="item.end" :empty-show="item.emptyShow" :list-count="item.listCount" :has-top="true" :refresh-size="80" @refresh="refresh" @more="more">
			<view class="cells">
				<view class="cell" v-for="(item, index) in item.listData" :key="index">
					<view class="cell__hd"><image mode="aspectFill" :src="item.images" /></view>
					<view class="cell__bd">
						<view class="name">{
   { item.title }}</view>
						<view class="des">{
   { item.description }}</view>
					</view>
				</view>
			</view>
		</scroll>
	</swiper-item>
</swiper>
...
以下代码在 /pages/home/home.vue 中


// 用于分页
let pageStart = 0
let pageSize = 15
// 列表数据
let testData = [
	{
    
		title: '这个绝望的世界没有存在的价值,所剩的只有痛楚',
		description: '思念、愿望什么的都是一场空,被这种虚幻的东西绊住脚,什么都做不到',
		images: '/static/logo.png' // 这里换成自己本地的图片地址
	}...]
// 引入组件, 修改路径
import Tab from '@/colorui/components/tab/index'
import Scroll from '@/colorui/components/scroll/index'


components:{
    Tab, Scroll},
// 当页面存在时,只会加载一次; onShow 是每次界面显示都会触发,包括手机的屏幕关闭和唤起
onLoad() {
    
        // 第一次进入 加载第一页的数据
	this.getList('refresh', pageStart)
},
methods: {
    
	getList(type, currentPage = 1) {
    
		let pageData = this.getCurrentData()


		pageData.requesting = true


		this.setCurrentData(pageData)


		uni.showNavigationBarLoading()


		setTimeout(() => {
    
			pageData.requesting = false


			uni.hideNavigationBarLoading()


			if (type === 'refresh') {
    
				pageData.listData = testData
				pageData.listCount = pageData.listData.length
				pageData.end = false //是否已经全部加载
				pageData.page = currentPage + 1
			} else {
    
				pageData.listData = pageData.listData.concat(testData)
				pageData.listCount = pageData.listData.length
				pageData.end = true
				pageData.page = currentPage + 1
			}


			this.setCurrentData(pageData)
		}, 100)
	},
	// 顶部tab切换事件
	toggleCategory(e) {
    
		this.duration = 0


		setTimeout(() => {
    
			this.categoryCur = e.index
		}, 0)
	},
	// 页面滑动切换事件
	swipeChange(e) {
    
		this.duration = 300


		setTimeout(() => {
    
			this.categoryCur = e.detail.current


			this.loadData()
		}, 0)
	},
	// 更新页面数据
	setCurrentData(pageData) {
    
		this.categoryData[this.categoryCur] = pageData
	},
	// 获取当前激活页面的数据
	getCurrentData() {
    
		return this.categoryData[this.categoryCur]
	},
	// 判断是否为加载新的页面,如果是去加载数据
	loadData() {
    
		let pageData = this.getCurrentData()
		if (pageData.listData.length == 0) {
    
			this.getList('refresh', pageStart)
		}
	},
	// 刷新数据
	refresh() {
    
		this.getList('refresh', pageStart)
	},
	// 加载更多
	more() {
    
		this.getList('more', this.getCurrentData().page)
	}
}

以下代码在 /pages/home/home.vue 中


<style lang="scss">
// 这里改成自己放置页面的位置,  tab 和 swiper 组件中的 scss 变量文件引用也要修改
@import '[email protected]/colorui/variables';


$top-height: 90rpx;


.top-wrap {
    
	position: fixed;
	left: 0;
	top: 0;
	/* #ifdef H5 */
	top: var(--window-top);
	/* #endif */
	width: 100%;
	background-color: #ffffff;
	z-index: 99;
}


swiper {
    
	height: 100vh;
}


.cells {
    
	background: #ffffff;
	margin-top: 20rpx;
}


.cell {
    
	display: flex;
	padding: 20rpx;


	&:not(:last-child) {
    
		border-bottom: 1rpx solid $lineColor;
	}


	&__hd {
    
		font-size: 0;


		image {
    
			width: 160rpx;
			height: 160rpx;
			margin-right: 20rpx;
			border-radius: 12rpx;
		}
	}


	&__bd {
    
		flex: 1;


		.name {
    
			@include line(2);
			font-size: 28rpx;
			margin-bottom: 12rpx;
		}


		.des {
    
			@include line(2);
			color: $mainBlack2;
			font-size: 24rpx;
		}
	}
}
</style>

页面效果如下:我们发现,引入了但是没有tab组件

查看dom结构可知:

.top-wrap使用了fixed定位,使用原生导航条没有影响,而我们使用的自定义导航条,也是定位。所以这里需要给 tab 设置 top 值,导航条的高度。还记得我们在实现自定义导航条时在App.vue中获取系统相关信息,赋值到了 Vue.prototype.CustomBar

// 以下代码在 /pages/home/home.vue中
// 动态设置 top值
<view :style="{
       top: CustomBar+'px'}" class="top-wrap tui-skeleton-rect">
	<tab id="category" :tab-data="categoryMenu" :cur-index="categoryCur" :size="80" :scroll="true" @change="toggleCategory"></tab>
</view>
...
data 中设置
CustomBar: this.CustomBar,

效果很帅气~~

这时大家在左右滑动的时候会发现一个问题,第一次从推荐滑动到精选集锦的时候,tab中的下边栏没有动,之后的滑动都会运动:

我们查看发现/colorui/tab/index.vue 中的 scrollByIndex 方法没有触发,在tab/index.vue中,我们发现实际上 curIndex 值是变化的,最简单的方式就是在监听curIndex变化的时候触发 scrollByIndex 方法:

watch: {
    
	curIndex(newVal, oldVal) {
    
		this.tabCur = newVal
		this.tabCurChange(newVal, oldVal)
                // 新增
		this.scrollByIndex(newVal)
	},
...

bug 解决

添加云函数

这里我们创建两个云函数,一名为article存放类别下的文章,一名为articleCategory,对应我们的顶部tab(我也不确定这么分是否最好)。创建完成后记得上传。

  • 初始化云数据库
// 以下代码在 db_init.json 中
"article_category": {
    
	"data": [ // 数据
		{
    
			"name": "掘金"
		},
		{
    
			"name": "HTML"
		},
		{
    
			"name": "CSS"
		},
		{
    
			"name": "JS"
		},
		{
    
			"name": "VUE"
		},
		{
    
			"name": "REACT"
		},
		{
    
			"name": "LeeCode"
		},
		{
    
			"name": "面试题"
		}
	    ],
	    "index": [{
     // 索引
	        "IndexName": "name", // 索引名称
	        "MgoKeySchema": {
     // 索引规则
	            "MgoIndexKeys": [{
    
	                "Name": "name", // 索引字段
	                "Direction": "1" // 索引方向,1:ASC-升序,-1:DESC-降序
	            }],
	            "MgoIsUnique": false // 索引是否唯一
	        }
	    }]
}

右键初始化我们的云数据库,如下

因为云数据库中有我们的user 数据,包括初始化和注册的,所以我们这里不覆盖。这是打开我们的云端web控制台,可以看到数据初始化成功:

接下来我们初始化文章表,由于文章会跟种类对应,所以文章每一项会有个categoryId字段,所以我们先初始化的类别表,初始化article表:

// 以下代码在 db_init.json 中


// 文章表
"article": {
    
	   "data": [ // 数据
		{
    
			"headImg": "https://images.weserv.nl/?url=https://p1.ssl.qhimgs1.com/sdr/400__/t012defb31c27aec7eb.jpg",
			"title": "这个绝望的世界没有存在的价值,所剩的只有痛楚1",
			"categoryId": "5ebd3b7f33bd17004e01c686",
			"description": "思念、愿望什么的都是一场空,被这种虚幻的东西绊住脚,什么都做不到",
			"date": "2020-03-09"
		},
		{
    
			"headImg": "https://images.weserv.nl/?url=https://p1.ssl.qhimgs1.com/sdr/400__/t012defb31c27aec7eb.jpg",
			"title": "这个绝望的世界没有存在的价值,所剩的只有痛楚2",
			"categoryId": "5ebd3b7f33bd17004e01c686",
			"description": "思念、愿望什么的都是一场空,被这种虚幻的东西绊住脚,什么都做不到",
			"date": "2020-03-08"
		},
		{
    
			"headImg": "https://images.weserv.nl/?url=https://p1.ssl.qhimgs1.com/sdr/400__/t012defb31c27aec7eb.jpg",
			"title": "这个绝望的世界没有存在的价值,所剩的只有痛楚css",
			"categoryId": "5ebd3b7f33bd17004e01c686",
			"description": "思念、愿望什么的都是一场空,被这种虚幻的东西绊住脚,什么都做不到",
			"date": "2020-03-08"
		}
	   ],
	   "index": [{
     // 索引
	       "IndexName": "date", // 索引名称
	       "MgoKeySchema": {
     // 索引规则
	           "MgoIndexKeys": [{
    
	               "Name": "date", // 索引字段
	               "Direction": "-1" // 索引方向,1:ASC-升序,-1:DESC-降序
	           }],
	           "MgoIsUnique": false // 索引是否唯一
	       }
	  }]
}

正常应该有PC管理平台,分配上传文章,并实现文章和类别的对应,这里就自己简化操作

这时开始写我们的页面逻辑:先把前端写死的假数据清除,包括categoryMenu 和 categoryData(记住数据格式)。

编写请求类别逻辑

// 以下代码在 /pages/home/home.vue 中
onLoad() {
    
        // 我得逻辑是先请求类别,在根据第一个类别获取文章
	this.getCategoryMenu()
	// this.getList('refresh', pageStart)
},
...
async getCategoryMenu() {
    
        // 以防内部执行出错
	try {
    
                // 还记得我们在user表时,创建了add 和get两个目录处理不同操作,
                // 这里也用了同样思路,万一想在小程序里实现delete 和put功能也方便,不是必须的
		const res = await this.$uniCloud('articleCategory', {
    
			type: 'get'
		})


		this.categoryMenu = ?
		this.categoryData = ?
                // 获取完类别 获取类别下的文章
		this.getList('refresh', pageStart)
	} catch (e) {
    
                // 在全局混入中定义了通用的报错信息
		this.$toast(this.errorMsg)
	}
}
...

书写articleCategory函数中逻辑:

// 一下代码在云函数 articleCategory中
'use strict';
const {
     get } = require('./get')
exports.main = async (event, context) => {
    
        // event 就是我们传递的 变量对象
	switch (event.type) {
    
		case 'get':
			return await get(event)
	}
};


// get 目录
const db = uniCloud.database()
exports.get = async (data) => {
    
        // 数据没有限制,表中有什么返回什么
	const collection = db.collection('article_category')
        // 查找最后一定要 get 一下
	return await collection.get()
}


// 记得上传运行云函数呦

云函数部署成功后,刷新我们的页面,发现有请求,书写页面逻辑:

// 请求处理数据代码
async getCategoryMenu() {
    
	try {
    
		const res = await this.$uniCloud('articleCategory', {
    
			type: 'get'
	  })


		this.categoryMenu = res.result.data
		this.categoryData = this.categoryMenu.map(item => {
    
			return {
    
				name: item.name,
				requesting: false,
				end: false,
				emptyShow: false,
				page: pageStart,
				listData: []
		}
		})
    // 请求第一类别文章
		// this.getList('refresh', pageStart)
	} catch (e) {
    
		this.$toast(this.errorMsg)
	}
}

这时发现页面显示不正确:

我们看一下tab/index.vue 插件代码,发现 11 行显示的是 item,而我们返回的是对象, 所以改成 item.name,这时我们的类别显示出来了。

编写请求文章逻辑

// home.vue中的getList方法
// 插件本身逻辑不用动,只需加入我们的请求
async getList(type, currentPage = 1) {
    
	let pageData = this.getCurrentData()


	pageData.requesting = true


	this.setCurrentData(pageData)
	// 自定义导航栏没有这个,需要可以自己加
	// uni.showNavigationBarLoading()


	// 请求数据, 第0页开始 1-10条
	let res = await this.$uniCloud('article', {
    
                // 类别
		categoryId: this.categoryMenu[this.categoryCur]._id,
		currentPage,// 第几页
		pageSize// 每页数量
	})
        // 请求的数据赋值
	testData = res.result.list


	setTimeout(() => {
    
		pageData.requesting = false


		// uni.hideNavigationBarLoading()


		if (type === 'refresh') {
    
			pageData.listData = testData
			pageData.listCount = pageData.listData.length
			pageData.end = false //是否已经全部加载
		        pageData.page = currentPage + 1
		} else if (testData.length === 10) {
    
			pageData.listData = pageData.listData.concat(testData)
			pageData.listCount = pageData.listData.length
			pageData.end = false
			pageData.page = currentPage + 1
		} else if (testData.length >= 0 && testData.length < 10) {
    
			pageData.listData = pageData.listData.concat(testData)
			pageData.listCount = pageData.listData.length
			pageData.end = true
			// pageData.page = currentPage + 1
		}


		this.setCurrentData(pageData)
		if (pageData.listData.length === 0) {
    
			pageData.emptyShow = true
		}
	}, 100)
}

// 以下代码在article云函数中, 相信大家一看会很清楚
'use strict';
const db = uniCloud.database()
const dbCmd = db.command


exports.main = async (event, context) => {
    
  const collection = db.collection('article')
  // 总条数
  let total = await collection.where({
    categoryId : event.categoryId}).count()


  // 获取文章列表
  let start = event.currentPage * event.pageSize
  let res = await collection.where({
    categoryId : event.categoryId}).orderBy('date','desc').skip(start).limit(event.pageSize).get();
  return {
    
	  total: total.total,
	  list: res.data
  }
};

数据出来了,三调正好和初始化的一样(没有出现图片的小伙伴,我们修改了images变量为 headImg,记得修改)

这里有个小问题

这里不是使用的border-bottom,而是根据tab 的item的宽度对应生成的,那这个初始长度哪里来的呢,查看tab/index.vue源码中data初始赋值:lineWidth: 100, // 下划 line 宽度,我们发现在初始化数据时并未触发动态生成下划线的方法:this.init(),因为插件是直接写死的数据,页面直接渲染,而我们是请求的数据,
所以初始时并未执行,同样一个watch即可:

以下代码在 tab/index.vue中
watch:{
    
...
	tabData(newVal) {
    
		this.init()
	}
}

我们发现未有数据时,会有个100px长的line,所以我们直接设置初始值为0即可

文章详情

由于存储到云数据库中时,都会自动生成_id,所以从文章列表页跳转到详情页,只要带着_id字段即可,在详情页面进行请求。

在pages目录右键创建page-details页面,由于文章内容以markdown或者富文本形式,我们可以使用rich-text组件,但是该组件对图片的预览,链接的跳转,包括事件的实现都不好,所以我们同样在插件市场使用parse富文本解析插件,首先实现列表跳转详情页:

// home.vue 中
<view class="cells">
        // navigator-hover 内置的点击样式
	<view hover-class="navigator-hover" @tap='toDetail(item)' class="cell" v-for="(item, index) in item.listData" :key="index">
		<view class="cell__hd"><image mode="aspectFill" :src="item.headImg" /></view>
		        <view class="cell__bd">
			<view class="name">{
    {
     item.title }}</view>
		        <view class="des">{
    {
     item.description }}</view>
		</view>
	</view>
</view>
...
toDetail(item) {
    
	this.$router('/page-details/page-details?_id='+item._id)
}


// 以下代码在 page-details.vue中
onLoad(e) {
    
    //路由中传参,使用onLoad接收
    console.log(e)
}

  • 富文本解析插件

下载zip解压到我们的项目中,/colorui/components/parse,在app.vue中引入样式
@import "colorui/components/parse/parse.css";,page-details中使用:

<view>
	<Parse :content="article"  @preview="preview" @navigate="navigate"></Parse>
</view>
...
import Parse from '@/colorui/components/parse/parse.vue'
data() {
    
	return {
    
		article: ''
	}
},
components:{
    Parse},
...
//预览图片
preview(src, e){
    },
// 跳转
navigate(href, e){
    }

插件支持富文本格式和markdown格式,先介绍markdown使用

markdown

  • 使用cnpm安装marked,在根目录下npm init -y && cnpm install marked --save
  • 详情目录中引入 import marked from ‘marked’
let str = `# uncertainty \r\n ## uncertainty \r\n ### uncertainty`
this.article = marked(str)

富文本

onLoad(e) {
    
	this.article = `<div><h1>你好啊</h1><h2>我很好</h2></div>`
},

详情接口

创建pageDetails云函数,上传并运行;初始化 article_details 集合:

// 以下代码在 db_init.json


"article_details": {
    
	"data": [
		{
    
                        // 这里的_id切记要和列表中返回的对应
			"id": "5ebd3c9c3c6376004c5cedbc",
			"content": "# 你好测不准 hello",
			"date": "2020-05-18"
		}
	],
	"index": [{
    
		"IndexName": "id", // 索引名称
		"MgoKeySchema": {
     // 索引规则
			  "MgoIndexKeys": [{
    
			      "Name": "id", // 索引字段
			      "Direction": "-1" // 索引方向,1:ASC-升序,-1:DESC-降序
			  }],
			  "MgoIsUnique": true // 索引是否唯一
		}
      }]
}

我使用的文章详情的id值对应列表中的_id值,实现查找;只需要点击列表跳转时把_id传到详情页,详情页中实现获取云端数据

// 以下代码在 page-details.vue中
onLoad(e) {
    
	this.getDetails(e)
},
...
async getDetails(e) {
    
	let res = await this.$uniCloud('pageDetails', {
    
		id: e._id
	})
	try{
    
		this.article = marked(res.result.data[0].content)
	}catch(e){
    
		//TODO handle the exception
	}


},

发现请求成功,数据也是我们刚刚上传的,富文本格式也是一样的;属性配置大家使用官网即可

如果后台返回的富文本中的媒体标签 img、video等的链接地址没有域名,只有目录如/upload/images/a.png,大家可以在 /parse/libs/wxDiscode.js中修改:

如果有a标签需要跳转,应为web-view组件,页面中使用原生导航条,web-view加载的第三方页面层级会最高。 App开发中使用富文本解析显示的结果可能会和小程序中不同,大家可自行尝试。
使用rich-text标签解析富文本的朋友,可能很难去改变内部的样式,还有不能给标签添加事件。如果功能简单的话大家可以使用字符串替换添加样式类,如:

后台返回数据 let str = ‘

你好测不准

拿百度富文本解析为例,一般使用者会直接把word文档拖入到富文本编辑器中,编辑器也会自动解析成dom结构,没有类名只有行内样式,样式比较固定。正因为如此如果到移动端了如果由样式修改的需要就要自己做字符串替换(后端返回的就是dom字符串)添加样式或者添加类名。

  1. 实现文本复制可以
str =<div class="wrapper">+ str + '</div>'

给返回的dom串包裹在wrapper类中,整体设置wrapper样式修改)

.wrapper{
    
	    user-select: text;
}

2.给图片添加类名,设置居中,最大宽度100%

str = str.replace(/<img/g,"<img class='my-img'")
.my-img{
    
	    display:block;    
	    max-width:100%;    
	    margin: 0 auto;
}

如果详情页面中有点赞,而列表页中为 onLoad 请求,那么在退出详情页返回列表页时不会在请求(如果使用onShow,会重新请求,但是列表页会有分页查询,发挥列表页时在请求会带来很多不便),这时要更新列表页的点赞数,确定点赞或取消点赞成功的话,可以使用 uni 自带的 uni. e m i t ( ) , 和 u n i . emit(),和 uni. emit()uni.on(),详情页触发,列表页监听。和vue 的EventBus一样

制作侧边弹出栏

因为我得页面只做了两个切换按钮,所以设置头像,设置字段就放在侧边抽屉:

我们要在自定义导航条组件中进行小修改

// 以下代码在cu-custom中
<view class="action" @tap="BackPage" v-if="isBack">
	<text :class="'cuIcon-' + icon"></text>
	<slot name="backText"></slot>
</view>


props中添加
icon: {
	type: String,
	default: 'back'
}

图标我们选用colorui自带的cuIcon-sort

创建组件colorui/components/drawer/drawer.vue。其实思路也比较简单,就是icon是back还是sort,如果是sort就显示侧边栏,加个简单的动画,直接上代码:

以下代码在 /colorui/components/drawer/drawer.vue
<template>
	<view class="drawer-class drawer" :class="[visible ? 'drawer-show' : '','drawer-left']">
		<view v-if="mask" class="drawer-mask" @tap="handleMaskClick" @touchmove.stop.prevent></view>
		<view class="drawer-container" @touchmove.stop.prevent>
			<slot></slot>
		</view>
	</view>


</template>


<script>
	export default {
     
		name:"Drawer",
		props: {
     
			visible: {
     
				type: Boolean,
				default: false
			},
			mask: {
     
				type: Boolean,
				default: true
			},
			maskClosable: {
     
				type: Boolean,
				default: true
			}
		},
		methods: {
     
			handleMaskClick() {
     
				if (!this.maskClosable) {
     
					return;
				}
				this.$emit('close', {
     });
			}
		}
	}
</script>


<style>
	.drawer {
     
		visibility: hidden;
	}
	.drawer-show {
     
		visibility: visible;
	}
	.drawer-show .drawer-mask {
     
		display: block;
		opacity: 1;
	}


	.drawer-show .drawer-container {
     
		opacity: 1;
	}


	.drawer-show.drawer-left .drawer-container{
     
		transform: translate3d(0, -50%, 0);
	}


	.drawer-mask {
     
		opacity: 0;
		position: fixed;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		z-index: 99999;
		background: rgba(0, 0, 0, 0.6);
		transition: all 0.3s ease-in-out;
	}


	.drawer-container {
     
		position: fixed;
		left: 50%;
		height: 100%;
		top: 0;
		transform: translate3d(-50%, -50%, 0);
		transform-origin: center;
		transition: all 0.3s ease-in-out;
		z-index: 99999;
		opacity: 0;
		background: #fff;
	}


	.drawer-left .drawer-container {
     
		left: 0;
		top: 50%;
		transform: translate3d(-100%, -50%, 0);
	}
</style>

cu-custom/cu-custom.vue中引入(布局样式大家可以根据自己的喜好书写):

// 以下代码在/colorui/components/cu-custom/cu-custom.vue中
...
<drawer mode="left" :visible="isleftDrawer" @close="closeDrawer">
	<view class="d-container h-100 flex flex-direction justify-center align-center">
		<view class="cu-avatar xl bg-red round cu-card shadow margin-bottom-xl">
			<!-- 随机头像 http://api.btstu.cn/doc/sjtx.php-->
			<image src="http://api.btstu.cn/sjtx/api.php" class="w-100 h-100"></image>
		</view>


		<view class="cu-list w-100 menu">
			<view class="cu-item arrow" @tap='handleNav(item)' v-for="(item, index) in navList" :key='index' hover-class="hover-class">
				<view class="content">
					<text class="text-grey" :class="['cuIcon-' + item.icon]"></text>
					<text class="text-grey">{
   {item.navName}}</text>
				</view>
			</view>
		</view>
	</view>
</drawer>
...
import Drawer from "../drawer/drawer"
...
data() {
  retrun {
    isleftDrawer: false
  }
},
components: {Drawer},
props: {
    ...
    icon: {
      type: String,
      default: 'back'
    }
}

小节

本小节是是博客demo的最后一部分,功能没多少,也算是我对使用云函数的一个小总结。大家可以自己的想法设计自己的小程序,自己书写云函数,小编也是刚入手,有写的不对的地方请大家指正;有跟着实现功能的朋友也可以自己去拓展,例如列表页实现骨架屏,大家可以去插件市场学习查看更多的功能实现,引入到自己的项目中。能力一般,水平有限,谢谢阅读!

想要学习更多精彩的实战技术教程?来图雀社区逛逛吧。

如果您觉得我们写得还不错,希望您能给️这篇文章点赞️哦

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_46042722/article/details/106206214

智能推荐

db.session.commit()报错问题_db.session try异常_传智博客的博客-程序员秘密

db.session.commit()报错问题try: db.session.add(t) db.session.commit() except Exception as e: db.session.rollback() return etry 包裹db.session.commit(),同时必须回滚 db.sess...

Django--(后台管理 小说书籍项目)_django小说积分解锁书籍_VanChaoi的博客-程序员秘密

基本流程(MTV)V:视图views:接受请求,逻辑处理,调用数据,输出响应 配置url:在自己的应用中配置path()M:模型model:负责与数据库交互 面向对象:模型对象,列表 定义模型类:指定属性及类型,以确定表的结构,迁移 后台管理:创建管理员,启动服务器,admin,注册admin.pyT:模板Template 加载:读取文件的内容代内存 渲染1.创建...

【JAVA】异常捕捉机制(3)—训练学习与总结_ 王昭的博客-程序员秘密

一、前言前面我们说到异常(Exception)的概念与分类和异常(Exception)的分类处理方式,所谓异常就是程序在运行的过程中出现的一些中断程序的例外问题,我们将其称之为异常。JDK 中定义了很多派生于Throwable类的异常类,来对应解决可能出现的各种各样的异常,Throwable下又派生出来了子类Error和Exception ,接下来我们进行编程训练学习并对其总结。二、编程学习...

JavaWeb 知识点汇总_以后只喝粥的博客-程序员秘密

JavaEEJavaEE 开发技术的简介产生为了满足开发多层体系结构的企业级应用的需求,Java公司的创始人Sun公司在早期的J2SE(Java 2 Platform Standard Edition)基础上,针对企业级应用的各种需求,提出了J2EE(Java 2 Plantform Enterprise Edition).特点定义:泛指那些为大型组织部门创建的应用程序。注:并不是特指为企业开发的应用软件。企业级应用程序一般具有以下特点:1)分布式:通过局域网运行在一个组织内部,或通过

oracle rr与yy日期格式_oracle的rr和yy_AmberSheng的博客-程序员秘密

oracle有效日期范围是公元前4712年1月1日到公元9999年12月31日。由于我使用的是中文版操作系统,但是oracle字符集是美国英语。所以为了使日期型数据正确显示和使用需要修改session。alter session set nls_date_language = ‘american’    oracle 9i以前版本使用的是yy日期格式。9i以及以后的版本使用的是rr日期格式。r

随便推点

资深JAVA讲师推荐的JAVA学习顺序_WZ_Yong的博客-程序员秘密

1.学习Java SE部分的内容,这部分是基础内容。掌握的越扎实,后面的内容学习起来就越容易。2.学习SQL和数据库的内容 ,这个也是非常基础的内容,举个例子:程序员的面试题中一定会有SQL部分的考试。3.学习HTML、css、JavaScript的内容。4.学习,JSP、Servlet、JavaBean、标记库、JSTL、MVC、JSP EL等Web编程的部分5.学习Struts 或者Spr

Vue3.0 组合式 API 分析与实践_vue3组合式api最佳实践_百度_开发者中心的博客-程序员秘密

本文带大家深入理解组合式 API 的设计详情,同时加入我们的实践经验总结。01 背景Vue3.x 版本的出现带来了许多令人眼前一亮的新特性,其中组合式 API(Composition API),一组附加的、基于功能的 API 被作为一种新的逻辑复用和代码组织的方式提供给了开发者,提供更加灵活的组合组件逻辑能力。同时组合式 API 通过使用简单的变量和函数,也提供了更好的类型推断,这使得通过新的 API 编写的代码即使不用 TypeScript 也可以通过 IDE 的支持方便的获得类型提示。组合式 API

【Boost】boost库asio详解2——strand与io_service区别_H-KING的博客-程序员秘密

无论如何使用,都能感觉到使用boost.asio实现服务器,不仅是一件非常轻松的事,而且代码很漂亮,逻辑也相当清晰,这点上很不同于ACE。使用io_service作为处理工作的work pool,可以看到,就是通过io_service.post投递一个Handler到io_service的队列,Handler在这个io_service.run内部得到执行,有可能你会发现,io_services

项目总监岗位职责与思考_增加项目总监岗位_数通畅联的博客-程序员秘密

软件行业项目都会对应配备一名项目经理作为项目相关事宜的统一管理者,的确对于独立的项目来讲,项目经理充当着很重要的角色与作用。不仅如此,项目管理中还存在一个很重要的角色,即项目总监。项目总监与项目经理不同,无论从意识上,还是能力上,都要高出多个跨度。项目总监作为项目工作的总负责人,其工作范围及精力不仅贯穿一个项目的全生命周期,而是综合管控多个项目,并执行统筹规划、整体把控、过程监督、技术支持、资...

ubuntu下配置普通用户使用USB设备权限(不需要sudo就可以开启usb相机)_不需要sudo访问/dev/ttyusb_ssokyfar的博客-程序员秘密

在ubuntu16.04下,使用奥比中光相机Astra STEREO S,运行驱动程序NiViewer或提供的Demo都需要加上sudo。为了不特地输sudo,可采取以下方法修改权限。1 .查看设备 打开终端输入:lsusb 得到如下的提示: Bus 001 Device 012: ID 05e3:0608 Genesys Logic, Inc. Hub2. 配置...

html 响应asp.net,(C#)IIS响应头敏感信息(Server/X-AspNet-Version等)过滤_dicong guan的博客-程序员秘密

如下图所示,如果不经过处理,相应头会出现如下敏感信息,影响服务安全。那么如何将这些信息隐藏呢?第一种方案:添加以下代码到文件global.asax.csprotected void Application_PreSendRequestHeaders(){Response.Headers.Remove("Server");Response.Headers.Remove("X-AspNet-Versi...

推荐文章

热门文章

相关标签