uni-app 快速入门 从零开始实现新闻资讯类跨端应用(更新中)

uni-app 快速入门 从零开始实现新闻资讯类跨端应用(更新中)


一、微信小程序基础

1.初始化文件目录结构

在这里插入图片描述

1、utils文件夹主要存放工具类
   pages目录存放所有页面代码
   app.js是文件入口
   app.jasn是项目配置
   app.wxss是全局样式表
2、index目录  
      index.js当前页面的业务逻辑
      index.json当前页面的配置文件
      index.wxml当前页面模板
      index.wxss当前页面的样式

2.数据绑定,条件判断、列表渲染

index.js

//必须使用Page构造器来构造页面
Page({
    
    
  /**
   * 页面的初始数据
   */
  data: {
    
    
    name:'hzy',
    is_ok:false,
    list:['1','2','3','4']
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    
    
    //改变数据的方法this.setData(obj)
    //this是指当前对象的实例,指向Page
    setTimeout(()=>{
    
    this.setData({
    
    name:'wwj',is_ok:true})},2000)
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {
    
    

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    
    

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {
    
    

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {
    
    

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {
    
    

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {
    
    

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {
    
    

  }
})

index.wxml

<!--pages/index/index.wxml-->
<text wx:if="{
    
    {is_ok}}">{
    
    {
    
    name}}</text>
<!--text 是行级元素,相当于span-->
<!--wx:if 控制标签是否渲染-->
<view wx:for="{
    
    {list}}">{
    
    {
    
    item}}</view>
<!--view 是块级元素,相当于div-->
<!--wx:for 控制标签循环渲染-->
<!--item 是指当前变量默认值
    index 是指当前下标默认值-->
<!--
	<view wx:for="{
    
    {list}}" wx:for-item="list">{
    
    {
    
    list}}</view> 
	等价于
	<view wx:for="{
    
    {list}}">{
    
    {
    
    item}}</view>
-->

二、uni-app基础

1.uni-app核心知识点概况

1.1 uni-app规范

①页面文件遵循Vue单文件组件(SFC)规范
在这里插入图片描述

②组件标签接近小程序规范
在这里插入图片描述
③接口能力(JS API)靠近小程序规范
在这里插入图片描述
④数据绑定及事件处理同Vue.js规范
⑤为兼容多端运行,建议使用flex布局进行开发

1.2 uni-app特色

①条件编译
在这里插入图片描述

②App端的Nvue开发法
③HTML5+

1.2 uni-app特色
在这里插入图片描述

2.uni-app环境搭建

①HBuilder直接创建
②vue-cli命令行创建

1、npm install -g @vue/cli 安装vue-cli
2、 vue -V  查看vue-cli版本,是否安装成功
3、 vue create -p dcloudio/uni-preset-vue 名称  安装uni-app项目

3.uni-app模板语法和数据绑定

<template>
	<view v-bind:class="className" v-on:click="open">
		{
    
    {
    
    title}}
	</view>
	<!-- v-bind:表示class的值为动态 -->
	<!-- 与小程序不同的是这里的v-bind:class,直接赋值,而不用{
    
    {
    
    }} -->
	<!-- :是v-bind:简写 -->
	<!-- @是v-on:简写 -->
</template>

<script>
	export default {
    
    
		//初始化数据
		data() {
    
    
			return {
    
    
				title: '两秒之前',
				className:'bul'
			}
		},
		onLoad() {
    
    
		},
		//自定义的事件方法写到methods里面
		methods: {
    
    
			//两秒之后修改title和className
			open(){
    
    
				setTimeout(()=>{
    
    
					this.title="两秒之后"
					this.className="gre"
					//this指向view实例,赋值方便,不用setstate和setData
				},2000)
			}
		}
	}
</script>

<style>
	.bul {
    
    
		color: #007AFF;
	}
	.gre{
    
    
		color: #4CD964;
	}
</style>

4.uni-app条件判断

<template>
	<view>
		<view>这是一直显示的内容</view>
		<view>
			<view v-if="show==='vue'">{
    
    {
    
    show}}</view>
			<view v-else-if="show==='react'">{
    
    {
    
    show}}</view>
			<view v-else>uni-app</view>
		</view>
		<!-- v-if: 指令的表达式返回true才会渲染 -->
		<button @click="isShow()">点击</button>
	</view>
</template>

<script>
	export default {
    
    
		data() {
    
    
			return {
    
    
				show: 'vue'
			}
		},
		onLoad() {
    
    },
		methods: {
    
    
			isShow() {
    
    
				this.show = this.show === 'vue' ? 'react' : 'vue'
			}
		}
	}
</script>

<style>
</style>


5.uni-app列表渲染

<template>
	<view>
		<!-- 列表渲染,通过数组来渲染列表 -->
		<view v-for="(item,index) in arr">{
    
    {
    
    index+1+' '+item}}</view>
		<!-- item:被迭代的数组元素的别名 -->
		<!-- index:被迭代的数组元素的下标 -->
		<view v-for="(value,key) in obj">{
    
    {
    
    key+' : '+value}}</view>
		<!-- 同时也可以遍历对象 可以获取value和key, 顺序不要弄错-->
	</view>
</template>

<script>
	export default {
    
    
		data() {
    
    
			return {
    
    
				arr:['uni-app','vue','react'],
				obj:{
    
    
					name:'hzy',
					age:18,
					type:'man',
				}
			}
		},
		onLoad() {
    
    },
		methods: {
    
    
			
		}
	}
</script>

<style>
</style>

在这里插入图片描述

6.uni-app基础组件使用

<template>
	<!-- uni-app基础组件使用,view也是基础组件,相当于html的div标签 -->
	<view>
		<view>view标签:</view>
		<view hover-class="redd">vue</view>
		<!-- hover-class指定点击下去的样式类 -->
		<view>react</view>
		<view>text标签:</view>
		<text>vue</text>
		<text>react</text>
		<!-- text标签相当于html的span,行内元素-->
		<scroll-view scroll-y="true" class="height" @scroll="scroll">
			<view v-for="item in 20">{
    
    {
    
    item}}</view>
		</scroll-view>
		<!-- 可滚动视图区域,必须设置高度 -->
		<!-- @scroll触发滚动时候的响应事件 -->
		<button type="default" @click="input">点击</button>
		<!-- <input type="text" v-bind:value="value"> -->
		<input type="text" v-model="value">
		<view>{
    
    {
    
    value}}</view>
		<!-- value前面得加上v-bind:表示他是个动态绑定的值,或者使用v-model,指的是双向绑定 -->
	</view>
</template>

<script>
	export default {
    
    
		data() {
    
    
			return {
    
    
				value: 'vue'
			}
		},
		onLoad() {
    
    },
		methods: {
    
    
			scroll(e) {
    
    
				console.log(e)
			},
			input() {
    
    
				this.value = this.value === 'vue' ? 'react' : 'vue'
			}
		}
	}
</script>

<style>
	.redd {
    
    
		border: 1px red solid;
	}

	.height {
    
    
		height: 50px;
		border: 1px red solid;
	}
</style>

在这里插入图片描述

7.uni-app自定义组件

index.vue 主页面

<template>
	<view>
		<hzyBtn color="blue" @change="change"></hzyBtn>
		<!-- 让组件去接收一个名为change的事件 -->
	</view>
</template>

<script>
	//在script标签下引用自定义组件,@表示的事根目录
	import but from '@/components/hzyBtn/hzyBtn.vue'
	export default {
    
    
		data() {
    
    
			return {
    
    

			}
		},
		onLoad() {
    
    },
		methods: {
    
    
			change(e) {
    
    
				console.log('页面的事件被触发,组件返回了:' + e)
			}
		}
	}
</script>

<style>
</style>

hzyBtn.vue 自定义按钮组件

<template>
	<view class="btn-box" :style="{color:color}" @click="butClick">
		点击
	</view>
</template>

<script>
	export default {
    
    
		props: {
    
    
			color: {
    
    
				type: String,
				default: '#000'
			},
			// color接受一个类型为String的对象,默认值为'#000'
		},
		// props接收属性
		data() {
    
    
			return {
    
    };
		},
		methods: {
    
    
			butClick() {
    
    
				console.log('组件内事件触发')
				this.$emit('change', this.color)
				//触发页面传来的‘change’事件
			}
		}
	}
</script>

<style>
	.btn-box {
    
    
		width: 200px;
		height: 100px;
		text-align: center;
		line-height: 100px;
		border: 1px red solid;
	}
</style>

页面
在这里插入图片描述
点击之后控制台打印
在这里插入图片描述

8.uni-app基础api使用

<template>
	<view></view>
</template>
<script>
	export default {
    
    
		data() {
    
    
			return {
    
    }
		},
		onLoad() {
    
    
			uni.getSystemInfo({
    
    
				success(res) {
    
    
					console.log('成功的回调', res)
				},
				fail(err) {
    
    
					console.log('失败的回调', err)
				},
				complete(res) {
    
    
					console.log('不管成功失败都会返回')
				}
			})
			//uni.getSystemInfo(obj)获取系统信息的api,异步的
		},
		// onLoad()只加载一次,监听页面加载,其参数为上个页面传递的数据,参数类型为Object
		methods: {
    
    }
	}
</script>
<style>
</style>

9.uni-app条件编译

<template>
	<view>
		<!-- 条件编译写在对应的注释里 -->
		<!-- 表示在h5和app端才进行这段代码的编译 -->
		<!-- #ifdef H5 || APP-PLUS-->
		<view>h5和app平台才显示</view>
		<!-- #endif -->
		<!-- #ifndef MP-WEIXIN -->
		<view>微信小程序不显示,其他都显示</view>
		<!-- #endif -->
	</view>
</template>

<script>
	export default {
    
    
		data() {
    
    
			return {
    
    }
		},
		onLoad() {
    
    

		},
		methods: {
    
    
			// #ifdef
			// #endif
		}
	}
</script>

<style>
	/* #ifdef */
	/* #endif */
</style>

10.uni-app页面布局

<template>
	<view class="content color">uni-app</view>
</template>
<script>
	export default {
    
    
		data() {
    
    
			return {
    
    }
		},
		onLoad() {
    
    

		},
		methods: {
    
    }
	}
</script>
<style>
	@import 'index.css';
	/* 引入外部css方式 */
	/* page{}等价于body{} */
	page {
    
    
		background-color: #007AFF;
	}

	/* 尺寸单位*/
	.content {
    
    
		/* px rpx rem vh vx*/
		font-size: 50px;
	}
</style>

10.uni-app生命周期

①应用生命周期(定义在app.vue页面中)

<script>
	// 生命周期分为三种:
	// 应用生命周期:定义在app.vue页面中
	// 页面生命周期
	// 组件生命周期
	export default {
    
    
		//应用初始化完成触发一次,全局只触发一次
		//可以做一些登录的事情,或者拿一些全局变量
		onLaunch: function() {
    
    
			console.log('App Launch')
		},
		//应用启动的时候,或者从后台进入前台会触发
		onShow: function() {
    
    
			console.log('App Show')
		},
		//应用从前台进入后台触发
		onHide: function() {
    
    
			console.log('App Hide')
		}
	}
</script>

<style>
	/*每个页面公共css */
</style>

②页面生命周期

<template>
	<view>
		<view>生命周期</view>
		<hzyText></hzyText>
	</view>
</template>
<script>
	import hzyText from '@/components/hzyText/hzyText.vue'
	export default {
    
    
		data() {
    
    
			return {
    
    }
		},
		//监听页面加载(页面加载之后执行,再次加载时候不执行,浏览器有缓存)
		onLoad() {
    
    
			console.log('page onLoad')
		},
		//监听页面初次渲染完成
		onReady() {
    
    
			//如果渲染速度快,会在页面进入动画完成前触发
			console.log('page onReady')
		},
		//监听页面显示,页面每次出现在屏幕上都会触发
		onShow(){
    
    
			console.log('page onShow')
		},
		//监听页面隐藏
		onHide(){
    
    
			console.log('page onHide')
		},
		//监听页面卸载
		onUnload(){
    
    
			console.log('page onUnload')
		},
		methods: {
    
    }
	}
</script>
<style>
</style>

③组件生命周期

<template>
	<view>
		hzyText组件
	</view>
</template>

<script>
	export default {
    
    
		data() {
    
    
			return {
    
    

			};
		},
		//在实例初始化之后,数据观测(data observer)和event/watcher事件配置之前被调用
		beforeCreate() {
    
    
			console.log('beforeCreate')
		},
		//实例创建完成之后立即调用,挂载阶段还没开始
		created() {
    
    
			console.log('created')
		},
		//挂载到实例上去之后调用
		mounted() {
    
    
			console.log('mounted')
		},
		//Vue实例销毁后调用
		destroyed() {
    
    
			console.log('destroyed')
		}
	}
</script>

<style>

</style>


三、项目基础配置

1.uni-app项目配置

①微信小程序,微信开发者工具要在设置的安全中打开服务端口,Hx才能启用
②安卓真机,手机连接电脑后,点击关于手机,点击五次版本号,进入开发者模式,开启usb调试,打开传输文件
③H5

2.uni-app目录结构了解

目录结构
1、componens - 自定义组件目录
2、pages - 页面存放目录 (里面页面的目录和文件需同名 例如:logon中的logon.vue)
3、static - 静态文件资源目录(图片和字体可以放在该目录)
4、unpackage - 编译后的文件存放目录
5、utils - 自定义存放工具类的目录
6、common - 自定义存放公用东西的目录
7、App.vue - 页面入口(可以做用户登录、项目初始话、全局样式更改等)
8、main.js - 应用入口
9、manifest.json - 项目配置
10、pages.json - 页面配置
11、readme.md - 项目基本情况
12、uni.scss - uni-app内置的常用样式变量

3.配置项目底部选项卡

pages.json

{
    
    
	//"pages":注册页面文件,页面窗口表现形式
	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
		{
    
    
			"path": "pages/index/index",
			"style": {
    
    
				"navigationBarTitleText": "uni-app",
				//app平台下特有窗口表现样式
				"app-plus": {
    
    

				},
				//微信小程序平台下特有窗口表现样式
				"mp-weixin": {
    
    

				},
				//h5平台下特有窗口表现样式
				"h5": {
    
    

				}
			}
		}
	    ,{
    
    
            "path" : "pages/about/about",
            "style" :                                                                                    
            {
    
    
                "navigationBarTitleText": "",
                "enablePullDownRefresh": false
            }
            
        }
        ,{
    
    
            "path" : "pages/my/my",
            "style" :                                                                                    
            {
    
    
                "navigationBarTitleText": "",
                "enablePullDownRefresh": false
            }
            
        }
    ],
	//"globalStyle"所有页面的默认配置,当"pages"下配置了和"globalStyle"一样的属性,就会将 "globalStyle"中的属性覆盖
	"globalStyle": {
    
    
		"navigationBarTextStyle": "black",
		"navigationBarTitleText": "uni-app",
		"navigationBarBackgroundColor": "#F8F8F8",
		"backgroundColor": "#F8F8F8"
	},
	//"tabBar"指得是tab栏的表现
	"tabBar":{
    
    
		"color":"#666",
		"selectedColor":"#ff5a5f",//选中时候的颜色
		"borderStyle":"black",
		//tabBar关联页面
		"list":[
			{
    
    
				"pagePath":"pages/index/index",
				"text":"首页",
				"iconPath":"static/home.png",//本地图片,大小40kb,尺寸81*81px
				"selectedIconPath":"static/home-active.png"//选中后图片
			},
			{
    
    
				"pagePath":"pages/about/about",
				"text":"关于",
				"iconPath":"static/follow.png",//本地图片,大小40kb,尺寸81*81px
				"selectedIconPath":"static/follow-active.png"//选中后图片
			},
			{
    
    
				"pagePath":"pages/my/my",
				"text":"我的",
				"iconPath":"static/my.png",//本地图片,大小40kb,尺寸81*81px
				"selectedIconPath":"static/my-active.png"//选中后图片
			}
		]
	}
}

总结:页面有个可以写onTabItemTap(e) { },这个生命周期函数是点击tebbar时候触发

4.在uni-app中使用sass

1、安装scss插件
在这里插入图片描述

<template>
	<view class="content box">
		<image class="logo" src="/static/logo.png"></image>
		<view class="text-area">
			<text class="title">{
    
    {
    
    title}}</text>
		</view>
	</view>
</template>

<script>
	export default {
    
    
		data() {
    
    
			return {
    
    
				title: 'Hello'
			}
		},
		onLoad() {
    
    
			
		},
		//tabbar点击触发的钩子
		onTabItemTap(e) {
    
    
			console.log(e)
		},
		methods: {
    
    

		}
	}
</script>

<style lang="scss">
	// scss可以写样式可以一层套一层
	$width : 200rpx; //变量定义用$
	.content {
    
    
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		//&表示父级 box和content是同级的样式
		&.box{
    
    
			border: 1px red solid;
		}
		.logo {
    
    
			height: $width;
			width: $width;
			margin-top: $width;
			margin-left: auto;
			margin-right: auto;
			margin-bottom: 50rpx;
		}
		.text-area {
    
    
			display: flex;
			justify-content: center;
			.title {
    
    
				font-size: 36rpx;
				color: #8f8f94;
			}
		}
	}
</style>

注意:style标签中得写lang="scss"来引用scss

四、uniCloud (云开发平台)的基础用法

1.认识 uniCloud开发

①uniCloud是基于serverless模式和js编程的云开发平台
②用javascript开发前后台真题业务,开发成本大幅下降,只需专注业务
③非H5,免域名使用服务器
④uniCloud开发流程
在这里插入图片描述
⑤uniCloud构成
云函数:
在这里插入图片描述
云数据库
云储存和CDN3

2.HBuilderX 中配置 uniCloud 环境

第一步:确保uni-app应用标识处于获取状态
在这里插入图片描述
第二步:右键cloudfunctions创建云服务空间,回来后再次右键选择关联云服务空间

!!! 注意:创建云函数后要点击右键部署到云开发平台

3. 使用 uniCloud web 控制台

方式:右键cloudfunctions就可以打开uniCloud web控制台

注意:①在云数据库中,所有字段和值都要用双引号。②localhost在uni-app中已经自动解决了跨域问题,如果要通过本机的ip访问云服务器就得在云控制台中进行跨域配置
在这里插入图片描述
③项目可以直接使用云存储的图片,视频的链接,也可以直接上传文件到云存储
在这里插入图片描述

4. 开始使用云函数

云函数 get_list/index.js

'use strict';
exports.main = async (event, context) => {
    
    
	//event为客户端上传的参数
	//context 包含了调用信息和运行状态,每次调用的上下文
	console.log('event : ', event)
	//返回数据给客户端
	return {
    
    
		"content":"成功",
		"event":event
	}
};

页面index.vue

	uniCloud.callFunction({
    
    
		//调用云函数
		name: "get_list",
		//传的参数
		data: {
    
    
			"name":"hzy"
		},
		//成功回调
		success(res) {
    
    
			console.log('云函数调用成功',res.result);
		},
		//失败回调
		fail(res) {
    
    
			console.log('云函数调用失败',res);
		}
	})

点击调用之后
在这里插入图片描述

5.云数据库的添加和删除

'use strict';
const db = uniCloud.database(); //1、获取数据库的引用
// 获取 `user` 集合的引用
exports.main = async (event, context) => {
    
    
	const collection = db.collection('user'); //2、获取集合的引用
	//给集合添加一条记录,就是添加一个json对象
	//添加await 之后数据才能同步
	let res = await collection.add(
		[
			{
    
    
				"name": "angular",
				"age": 60
			},
			{
    
    
				"name": "gofa",
				"age": 100
			}
		]
	)
	//删除云数据库中的记录,要传入ID,先用doc()找到,再用remove()删除
	let res = await collection.doc('5fd8e308fb0f850001897146').remove()
	return {
    
    }
};

在这里插入图片描述

6.数据库的更新和查找

①更新数据

'use strict';
const db = uniCloud.database(); //1、获取数据库的引用
// 获取 `user` 集合的引用
exports.main = async (event, context) => {
    
    
	const collection = db.collection('user'); //2、获取集合的引用
	//更新数据库记录,先用doc()找到,再用update()更新,也可以用set()来更新
	//可以增加字段,减少字段,修改字段
	//update() 只能更新存在的记录
	//set 如果记录存在就更新,如果不存在就添加
	const res = await collection.doc('5fd8e308fb0f850001897146').update(
		name:"likeReact"
	)
	return {
    
    }
};

②查找数据

'use strict';
const db = uniCloud.database(); //1、获取数据库的引用
// 获取 `user` 集合的引用
exports.main = async (event, context) => {
    
    
	const collection = db.collection('user'); //2、获取集合的引用
	//查询,先通过doc()找到这条记录,再用get()把记录拿到
	//doc()只能应用id,不能应用其他字段
	const docRes = await collection.doc("5fd8e308fb0f850001897146").get()
	//where(),可以通过字段名称来找到记录,注意,需要传对象
	const whereRes = await collection.where({
    
    
		name:"vue"
	}).get()
	console.log(JSON.stringify(whereRes))
	return {
    
    }
};

7.使用云储存上传文件

//选择图片的api
				uni.chooseImage({
    
    
					count: 1, //只允许上传一张图片
					success(res) {
    
    
						const tempFilePath = res.tempFilePaths[0] //获取第一张图片的地址
						//上传到云储存中
						console.log(tempFilePath)
						uniCloud.uploadFile({
    
    
							filePath: tempFilePath,
							cloudPath:"123456.jpg",//名称必须和上传的图片名称一致
							success(res) {
    
    
								console.log('success', res)
								//返回的filePath和fileID都可以直接拿来使用
							},
							fail() {
    
    
								console.log('fail', res)
							}
						})
					},
					fail(res) {
    
    
						console.log(res)
					}
				})

五、首页功能模块

1.项目初始化

1、创建db_init_json,初始化云数据库
2、配置tabbar
创建tabbar目录,将首页,关注,我的,三个页面放到tabbar文件目录下
在这里插入图片描述
在pages.json文件中配置tabbar

	"tabBar": {
    
    
		"color": "#666",
		"selectedColor": "#f07373",
		"backgroundColor": "#fff",
		"list": [{
    
    
			"pagePath": "pages/tabbar/index/index",
			"iconPath": "static/home.png",
			"selectedIconPath": "static/home.png",
			"text": "首页"
		}, {
    
    
			"pagePath": "pages/tabbar/follow/follow",
			"iconPath": "static/follow.png",
			"selectedIconPath": "static/follow-active.png",
			"text": "关注"
		}, {
    
    
			"pagePath": "pages/tabbar/my/my",
			"iconPath": "static/my.png",
			"selectedIconPath": "static/my-active.png",
			"text": "我的"
		}]
	}

2. 自定义导航栏

navbar.vue

<template>
	<view class="navbar">
		<view class="navbar-fixed">
			<view class="navbar-serach">
				<view class="navbar-serach_icon"></view>
				<view class="navbar-serach_text">请输入...</view>
			</view>
		</view>
		<view style="height: 45px;"></view>
	</view>
</template>

<script>
	export default {
    
    
		data() {
    
    
			return {
    
    
				
			};
		}
	}
</script>

<style lang="scss">
	.navbar{
    
    
		.navbar-fixed{
    
    
			position: fixed;//固定在顶部
			top:0px;
			left: 0px;
			z-index: 99;
			display: flex;
			justify-content: center;//水平居中
			align-items: center;
			width: 100%;
			height: 45px;//常见导航栏都是45px
			background-color: $mk-base-color;
			padding: 0 15px;
			box-sizing: border-box;//只在盒内显示
			.navbar-serach{
    
    
				display: flex;//flex布局,横排
				align-items: center;//垂直居中
				height: 30px;
				border-radius: 30px;//和高度一样就能实现圆角
				width: 100%;
				padding: 0 10px;
				background-color: #fff;
				.navbar-serach_icon{
    
    
					width: 10px;
					height: 10px;
					border: 1px red solid;
					margin-right: 10px;
				}
				.navbar-serach_text{
    
    
					font-size: 12px;
					color: #999;
				}
			}
		}
	}
</style>

在这里插入图片描述
注意:①
一些常用的样式变量可以写到uni.scss中,方便以后直接通过名字引用
在这里插入图片描述
②uni-app有个easyCom模式,只要组件的存放位置是"@/components/组件名/组件名.vue",就可以无需引用,直接使用,而且还是局部引用,非常点赞

3.导航栏适配小程序

在小程序中是有一个状态栏的,所以做完H5之后还要添加一个状态栏,在navbar中做一下适配,在不同设配上小程序状态栏的高度不一样,导航栏的高度就等于胶囊的buttom减去状态栏高度在加top减去状态栏高度

<template>
	<view class="navbar">
		<view class="navbar-fixed">
			<!-- 状态栏 -->
			<view :style="{height:statusBarHeight+'px'}"></view>
			<!-- 导航栏内容 -->
			<view class="navbar-conten" :style="{height:navBarHeight+'px',width:navBarWidth+'px'}">
				<view class="navbar-serach">
					<view class="navbar-serach_icon">
						<uni-icons type="search" size="16px" color="#999"></uni-icons>
						<!-- <text class="iconfont iconicon-test"></text> -->
					</view>
					<view class="navbar-serach_text">请输入...</view>
				</view>
			</view>
		</view>
		<view :style="{height:navBarHeight+statusBarHeight+'px'}"></view>
	</view>
</template>

<script>
	export default {
    
    
		data() {
    
    
			return {
    
    
				statusBarHeight: 20, //状态栏高度
				navBarHeight: 45, //导航栏高度
				navBarWidth: 375, //内容宽度
			};
		},
		//组件加载的时候执行
		created() {
    
    
			//同步获取手机系统信息
			const info = uni.getSystemInfoSync()
			//设置状态栏高度(H5的状态栏高度是0)
			this.statusBarHeight = info.statusBarHeight
			this.navBarWidth = info.windowWidth
			// #ifndef H5 || APP-PLUS || MP-ALIPAY
			//H5 app mp-alipay 不支持uni.getMenuButtonBoundingClientRect()这个api
			//获取胶囊的位置信息
			const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
			//(胶囊底部高度-状态栏高度)+(胶囊顶部高度-状态栏高度)=导航栏高度
			this.navBarHeight = (menuButtonInfo.bottom - info.statusBarHeight) + (menuButtonInfo.top - info.statusBarHeight) + 4
			//内容宽度
			this.navBarWidth = menuButtonInfo.left
			// #endif
		}
	}
</script>

<style lang="scss">
	@import "../../common/icons/css/icons.css";
	.navbar {
    
    
		.navbar-fixed {
    
    
			position: fixed; //固定在顶部
			top: 0px;
			left: 0px;
			z-index: 99;
			width: 100%;
			background-color: $mk-base-color;

			.navbar-conten {
    
    
				display: flex;
				justify-content: center; //水平居中
				align-items: center;
				padding: 0 15px;
				box-sizing: border-box; //只在盒内显示


				.navbar-serach {
    
    
					display: flex; //flex布局,横排
					align-items: center; //垂直居中
					height: 30px;
					border-radius: 30px; //和高度一样就能实现圆角
					width: 100%;
					padding: 0 10px;
					background-color: #fff;

					.navbar-serach_icon {
    
    
						margin-right: 10px;
					}

					.navbar-serach_text {
    
    
						font-size: 12px;
						color: #999;
					}
				}
			}
		}
	}
</style>

微信状态栏的前景色是黑色的,如果要改成白色,则需要在pages.json中配置
在这里插入图片描述

4 .使用字体图标

①复制阿里图标库的icon代码
在这里插入图片描述
直接在text中通过类名引用

<text class="iconfont iconicon-test"></text>

②在插件市场安装icons图标插件
直接通过组件去引用

<uni-icons type="search" size="16px" color="#999"></uni-icons>

5.选项卡展示

<template>
	<view class="tab">
		<scroll-view class="tab-scroll" scroll-x>
			<view class="tab-scroll_box">
				<view v-for="item in list" :key="index" class="tab-scroll_item">
					{
    
    {
    
    item.name}}
				</view>
			</view>
		</scroll-view>
		<view class="tab-icons">
			<uni-icons type="gear" size="26" color="#666"></uni-icons>
		</view>
	</view>
</template>

<script>
	export default {
    
    
		data() {
    
    
			return {
    
    
				list: [{
    
    
					name: 'uni-app'
				}, {
    
    
					name: 'uni-app'
				}, {
    
    
					name: 'uni-app'
				}, {
    
    
					name: 'uni-app'
				}, {
    
    
					name: 'uni-app'
				}, {
    
    
					name: 'uni-app'
				}, {
    
    
					name: 'uni-app'
				}]
			};
		}
	}
</script>

<style lang="scss">
	.tab {
    
    
		display: flex;
		width: 100%;
		border-bottom: 1px #f5f5f5 solid;
		background-color: #fff;
		box-sizing: border-box; //盒内元素

		.tab-scroll_box {
    
    
			display: flex;
			flex-wrap: nowrap; //不换行
			align-items: center; //垂直居中
			box-sizing: border-box; //盒内元素
			height: 45px;

			.tab-scroll_item {
    
    
				flex-shrink: 0; //不挤压
				padding: 0 10px;
				color: #333;
				font-size: 14px;
			}
		}

		.tab-icons {
    
    
			position: relative;
			display: flex;
			justify-content: center; //水平居中
			align-items: center; //垂直居中
			width: 45px;
			&::after{
    
    
				// &::after指的是该元素后面的内容
				content: '';
				position: absolute;
				top:12px;
				bottom:12px;
				left: 0;
				width: 1px;
				background-color: #ddd;
			}
		}
	}
</style>

在这里插入图片描述

6.选项卡数据初始化

云函数get_label

'use strict';
const db = uniCloud.database()//创建数据库的引用
exports.main = async (event, context) => {
    
    
	let label = await db.collection('label').get()//获取label表数据
	// await 意思是等一下,等着db.collection('label').get()这个函数执行完毕,得到值后再赋值给label
	//返回数据给客户端
	return {
    
    
		code:200,
		msg:'数据请求成功',
		data:label.data
	}
};

首页index.vue

<template>
	<view class="content">
		<navbar></navbar>
		<tab :list="tabList"></tab>
		<!-- <view v-for="item in 100">{
    
    {
    
    item}}</view> -->
	</view>
</template>

<script>
	//uni-app有个easyCom模式,只要组件的存放位置是"@/components/组件名/组件名.vue",就可以无需引用,直接使用
	//而且还是局部引用
	// import navbar from "@/components/navbar/navbar.vue"
	export default {
    
    
		data() {
    
    
			return {
    
    
				tabList: []
			}
		},
		onLoad() {
    
    
			this.getLabel()
		},
		methods: {
    
    
			getLabel() {
    
    
				uniCloud.callFunction({
    
    
					name: 'get_label',
				}).then((res) => {
    
    
					this.tabList = res.result.data
				})
			}
		}
	}
</script>

<style lang="scss">

</style>

tab.vue

<template>
	<view class="tab">
		<scroll-view class="tab-scroll" scroll-x>
			<view class="tab-scroll_box">
				<view v-for="(item,index) in list" :key="index" class="tab-scroll_item">
					{
    
    {
    
    item.name}}
				</view>
			</view>
		</scroll-view>
		<view class="tab-icons">
			<uni-icons type="gear" size="26" color="#666"></uni-icons>
		</view>
	</view>
</template>

<script>
	export default {
    
    
		props:{
    
    
			//接受一个list属性,类型是数组,默认值是空数组
			list: {
    
    
				type: Array,
				default:[]
			}
		},
		data() {
    
    
			return {
    
    
			};
		}

	}
</script>

<style lang="scss">
	.tab {
    
    
		display: flex;
		width: 100%;
		border-bottom: 1px #f5f5f5 solid;
		background-color: #fff;
		box-sizing: border-box; //盒内元素

		.tab-scroll_box {
    
    
			display: flex;
			flex-wrap: nowrap; //不换行
			align-items: center; //垂直居中
			box-sizing: border-box; //盒内元素
			height: 45px;

			.tab-scroll_item {
    
    
				flex-shrink: 0; //不挤压
				padding: 0 10px;
				color: #333;
				font-size: 14px;
			}
		}

		.tab-icons {
    
    
			position: relative;
			display: flex;
			justify-content: center; //水平居中
			align-items: center; //垂直居中
			width: 45px;

			&::after {
    
    
				// &::after指的是该元素后面的内容
				content: '';
				position: absolute;
				top: 12px;
				bottom: 12px;
				left: 0;
				width: 1px;
				background-color: #ddd;
			}
		}
	}
</style>

7.封装数据请求

api/index.js

//批量导出文件(自动导出)

const requireApi = require.context(
	//参数:读取文件的路径,是否遍历文件的子目录,匹配文件的正则
	//api目录的相对路径
	'.',
	//是否查询子目录
	false,
	//查询文件的一个后缀,只匹配.js后缀
	/.js$/
)
let module = {
    
    }
requireApi.keys().forEach((key, index) => {
    
    
	//requireApi.keys() 匹配好的文件列表 [./index.js,./list.js,...]
	//forEach相当于map
	if (key === './index.js') return
	Object.assign(module, requireApi(key)) //对象合并
})
export default module

api/list.js

/* 存放所有接口请求的api */
import $http from '../http.js'
export const get_label = (data) => {
    
    
	return $http({
    
    
		url:'get_label',
		data
	})
}

http.js

/* 封装网络请求 */
export default function $http(options){
    
    
	const {
    
    url,data} = options //url接口名称,data传给接口的数据
	return new Promise((reslove, reject) => {
    
    
		uniCloud.callFunction({
    
    
			name: url,
			data
		}).then((res)=>{
    
    
			if(res.result.code===200){
    
    
				reslove(res.result)
			}else{
    
    
				reject(res.result)
			}
		}).catch((err)=>{
    
    reject(err)})
	})
}

main.js

import Vue from 'vue'
import App from './App'
import api from './common/api'
Vue.config.productionTip = false
Vue.prototype.$api = api //绑定到vue实例中,之后就可以直接通过this.$api调用(方便)
App.mpType = 'app'

const app = new Vue({
    
    
    ...App
})
app.$mount()

tabbar/index.vue

<template>
	<view class="content">
		<navbar></navbar>
		<tab :list="tabList"></tab>
		<!-- <view v-for="item in 100">{
    
    {
    
    item}}</view> -->
	</view>
</template>

<script>
	//uni-app有个easyCom模式,只要组件的存放位置是"@/components/组件名/组件名.vue",就可以无需引用,直接使用
	//而且还是局部引用
	// import navbar from "@/components/navbar/navbar.vue"
	export default {
    
    
		data() {
    
    
			return {
    
    
				tabList: []
			}
		},
		onLoad() {
    
    
			this.getLabel()//获取选项卡数据
		},
		methods: {
    
    
			getLabel() {
    
    
				this.$api.get_label(
					{
    
    
						name:'get_label'
					}
				).then((res)=>{
    
    
					const {
    
    data} = res
					this.tabList = data
					console.log(res)
				})
			}
		}
	}
</script>

<style lang="scss">

</style>

8.选项卡切换

tab.vue

<template>
	<view class="tab">
		<scroll-view class="tab-scroll" scroll-x>
			<view class="tab-scroll_box">
				<view v-for="(item,index) in list" :class="{active:activeIndex===index}" :key="index" class="tab-scroll_item" @click="clickTab(item,index)">
					{
    
    {
    
    item.name}}
				</view>
			</view>
		</scroll-view>
		<view class="tab-icons">
			<uni-icons type="gear" size="26" color="#666"></uni-icons>
		</view>
	</view>
</template>

<script>
	export default {
    
    
		props: {
    
    
			//接受一个list属性,类型是数组,默认值是空数组
			list: {
    
    
				type: Array,
				default: []
			}
		},
		data() {
    
    
			return {
    
    
				activeIndex:0
			};
		},
		methods: {
    
    
			//点击tab
			clickTab(item, index) {
    
    
				this.activeIndex = index
				this.$emit('tab',{
    
    
					data:item,
					index
				})//调用父组件的方法,将当前tab的内容和下标传给父组件
			}
		}

	}
</script>

<style lang="scss">
	.tab {
    
    
		display: flex;
		width: 100%;
		border-bottom: 1px #f5f5f5 solid;
		background-color: #fff;
		box-sizing: border-box; //盒内元素

		.tab-scroll_box {
    
    
			display: flex;
			flex-wrap: nowrap; //不换行
			align-items: center; //垂直居中
			box-sizing: border-box; //盒内元素
			height: 45px;

			.tab-scroll_item {
    
    
				flex-shrink: 0; //不挤压
				padding: 0 10px;
				color: #333;
				font-size: 14px;
				&.active{
    
    
					color: $mk-base-color;
				}
			}
		}

		.tab-icons {
    
    
			position: relative;
			display: flex;
			justify-content: center; //水平居中
			align-items: center; //垂直居中
			width: 45px;

			&::after {
    
    
				// &::after指的是该元素后面的内容
				content: '';
				position: absolute;
				top: 12px;
				bottom: 12px;
				left: 0;
				width: 1px;
				background-color: #ddd;
			}
		}
	}
</style>

:class="{active:activeIndex===index}" 动态类的定义,当activeIndex等于index,该标签就会多一个active的类
tabbar/index.vue

<template>
	<view class="content">
		<navbar></navbar>
		<tab :list="tabList" @tab='tab'></tab>
		<!-- <view v-for="item in 100">{
    
    {
    
    item}}</view> -->
	</view>
</template>

<script>
	//uni-app有个easyCom模式,只要组件的存放位置是"@/components/组件名/组件名.vue",就可以无需引用,直接使用
	//而且还是局部引用
	// import navbar from "@/components/navbar/navbar.vue"
	export default {
    
    
		data() {
    
    
			return {
    
    
				tabList: []
			}
		},
		onLoad() {
    
    
			this.getLabel()//获取选项卡数据
		},
		methods: {
    
    
			//获取选项卡数据方法
			getLabel() {
    
    
				this.$api.get_label(
					{
    
    
						name:'get_label'
					}
				).then((res)=>{
    
    
					const {
    
    data} = res
					this.tabList = data
					console.log(res)
				})
			},
			//tab切换时候触发的方法
			tab({
    
    data,index}){
    
    
				console.log(data,index)
			}
		}
	}
</script>

<style lang="scss">

</style>

9.基础卡片实现

components/list-scroll.vue

<template>
		<view class="scroll">
			<scroll-view scroll-y class="list-scroll">
				<view>
					<!-- slot 插槽 -->
					<slot></slot>
				</view>
			</scroll-view>
		</view>
</template>

<script>
	export default {
    
    
		data() {
    
    
			return {
    
    
			};
		}
	}
</script>

<style lang="scss">
	.scroll {
    
    
		flex: 1;
		overflow: hidden; //溢出隐藏
		box-sizing: border-box; // 盒内元素
		.list-scroll {
    
    
			height: 100%;
			display: flex;
			flex-direction: column; //垂直排列
		}
	}
</style>

list-card.vue

<template>
	<view>
		<!-- 基础卡片 -->
		<view class="listcard">
			<view class="listcard-image">
				<image src="../../static/logo.png" mode="aspectFill"></image>
			</view>
			<view class="listcard-content">
				<view class="listcard-content__title">
					<text>uni-app开发框架uni-app开发框架uni-app开发框架uni-app开发框架uni-app开发框架uni-app开发框架</text>
				</view>
				<view class="listcard-content__des">
					<view class="listcard-content__des-label">
						<view class="listcard-content__des-label-item">前端</view>
					</view>
					<view class="listcard-content__des-browse">120浏览</view>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	export default {
    
    
		data() {
    
    
			return {
    
    

			};
		}
	}
</script>

<style lang="scss">
	.listcard {
    
    
		display: flex;
		padding: 10px;
		margin: 10px;
		border-radius: 5px;
		box-shadow: 0 0 5px 1px rgba($color:#000000, $alpha:0.1);
		//5px模糊效果,1px扩散
		box-sizing: border-box;

		.listcard-image {
    
    
			flex-shrink: 0;//防止挤压,正常显示
			width: 60px;
			height: 60px;
			border-radius: 5px;
			overflow: hidden;

			image {
    
    
				//使图片全部正常显示
				width: 100%;
				height: 100%;
			}
		}

		.listcard-content {
    
    
			display: flex;
			flex-direction: column; //垂直排列
			justify-content: space-between;//垂直上下对齐
			padding-left: 10px;
			width: 100%;
			.listcard-content__title{
    
    
				font-size: 14px;
				color: #333;
				font-weight: 400;
				line-height: 1.2;
				text{
    
    
					overflow: hidden;//溢出隐藏
					text-overflow: ellipsis;//当文本溢出包含元素省略符号显示
					display: -webkit-box;// 将对象作为弹性伸缩盒子模型显示
					-webkit-line-clamp:2;//只显示两行
					-webkit-box-orient:vertical; // 从上到下垂直排列子元素
				}
			}
			.listcard-content__des{
    
    
				display: flex;
				justify-content: space-between;//左右两边对齐
				font-size: 12px;
				.listcard-content__des-label{
    
    
					display: flex;
					.listcard-content__des-label-item{
    
    
						padding: 0 5px;
						margin-right: 5px;
						border-radius: 15px;
						color: $mk-base-color;
						border: 1px $mk-base-color solid;
					}
				}
				.listcard-content__des-browse{
    
    
					color: #999;
					line-height: 1.5;
				}
			}
		}
	}
</style>

tabbar/index.vue

<template>
	<view class="home">
		<navbar></navbar>
		<tab :list="tabList" @tab='tab'></tab>
		<list-scroll>
			<list-card v-for="item in 5"></list-card>
		</list-scroll>
	</view>
</template>

<script>
	//uni-app有个easyCom模式,只要组件的存放位置是"@/components/组件名/组件名.vue",就可以无需引用,直接使用
	//而且还是局部引用
	// import navbar from "@/components/navbar/navbar.vue"
	export default {
    
    
		data() {
    
    
			return {
    
    
				tabList: []
			}
		},
		onLoad() {
    
    
			this.getLabel()//获取选项卡数据
		},
		methods: {
    
    
			//获取选项卡数据方法
			getLabel() {
    
    
				this.$api.get_label(
					{
    
    
						name:'get_label'
					}
				).then((res)=>{
    
    
					const {
    
    data} = res
					this.tabList = data
					console.log(res)
				})
			},
			//tab切换时候触发的方法
			tab({
    
    data,index}){
    
    
				console.log(data,index)
			}
		}
	}
</script>

<style lang="scss">
	page{
    
    
		height: 100%;
		display: flex;
	}
	.home{
    
    
		display: flex;
		flex-direction: column;//垂直排列
		flex:1;//等价于flex-grow: 1;flex-shrink: 1;flex-basis: 0%;
		overflow: hidden;//溢出隐藏
		// border: 1px red solid;
	}
</style>

在这里插入图片描述

10.更多卡片视图实现

components/list-card.vue

<template>
	<view>
		<!-- 基础卡片 -->
		<view v-if="mode==='base'" class="listcard">
			<view class="listcard-image">
				<image src="../../static/logo.png" mode="aspectFill"></image>
			</view>
			<view class="listcard-content">
				<view class="listcard-content__title">
					<text>uni-app开发框架uni-app开发框架uni-app开发框架uni-app开发框架uni-app开发框架uni-app开发框架</text>
				</view>
				<view class="listcard-content__des">
					<view class="listcard-content__des-label">
						<view class="listcard-content__des-label-item">前端</view>
					</view>
					<view class="listcard-content__des-browse">120浏览</view>
				</view>
			</view>
		</view>
		<!-- 多图模式卡片 -->
		<view v-if="mode==='column'" class="listcard mode-column">
			<view class="listcard-content">
				<view class="listcard-content__title">
					<text>uni-app开发框架</text>
				</view>
				<view class="listcard-image">
					<view v-for="item in 3" :key="item" class="listcard-image__item">
						<image src="../../static/logo.png" mode="aspectFill"></image>
					</view>
				</view>
				<view class="listcard-content__des">
					<view class="listcard-content__des-label">
						<view class="listcard-content__des-label-item">前端</view>
					</view>
					<view class="listcard-content__des-browse">120浏览</view>
				</view>
			</view>
		</view>
		<!-- 大图模式卡片 -->
		<view v-if="mode==='image'" class="listcard mode-image">
			<view class="listcard-image">
					<image src="../../static/logo.png" mode="aspectFill"></image>
			</view>
			<view class="listcard-content">
				<view class="listcard-content__title">
					<text>uni-app开发框架</text>
				</view>
				<view class="listcard-content__des">
					<view class="listcard-content__des-label">
						<view class="listcard-content__des-label-item">前端</view>
					</view>
					<view class="listcard-content__des-browse">120浏览</view>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	export default {
    
    
		props:{
    
    
			//通过接受mode来判断,显示什么模式的卡片,mode是String类型的字符串,默认值是base
			//base基础卡片,column多图卡片,image,大图卡片
			mode:{
    
    
				type:String,
				default:'base'
			}
		},
		data() {
    
    
			return {
    
    

			};
		}
	}
</script>

<style lang="scss">
	.listcard {
    
    
		display: flex;
		padding: 10px;
		margin: 10px;
		border-radius: 5px;
		box-shadow: 0 0 5px 1px rgba($color:#000000, $alpha:0.1);
		//5px模糊效果,1px扩散
		box-sizing: border-box;

		.listcard-image {
    
    
			flex-shrink: 0; //防止挤压,正常显示
			width: 60px;
			height: 60px;
			border-radius: 5px;
			overflow: hidden;

			image {
    
    
				//使图片全部正常显示
				width: 100%;
				height: 100%;
			}
		}

		.listcard-content {
    
    
			display: flex;
			flex-direction: column; //垂直排列
			justify-content: space-between; //垂直上下对齐
			padding-left: 10px;
			width: 100%;

			.listcard-content__title {
    
    
				font-size: 14px;
				color: #333;
				font-weight: 400;
				line-height: 1.2;

				text {
    
    
					overflow: hidden; //溢出隐藏
					text-overflow: ellipsis; //当文本溢出包含元素省略符号显示
					display: -webkit-box; // 将对象作为弹性伸缩盒子模型显示
					-webkit-line-clamp: 2; //只显示两行
					-webkit-box-orient: vertical; // 从上到下垂直排列子元素
				}
			}

			.listcard-content__des {
    
    
				display: flex;
				justify-content: space-between; //左右两边对齐
				font-size: 12px;

				.listcard-content__des-label {
    
    
					display: flex;

					.listcard-content__des-label-item {
    
    
						padding: 0 5px;
						margin-right: 5px;
						border-radius: 15px;
						color: $mk-base-color;
						border: 1px $mk-base-color solid;
					}
				}

				.listcard-content__des-browse {
    
    
					color: #999;
					line-height: 1.5;
				}
			}
		}
		&.mode-column{
    
    
			.listcard-content{
    
    
				width: 100%;
				padding-left: 0px;
			}
			.listcard-image{
    
    
				display: flex;
				margin-top: 10px;
				width: 100%;
				height: 70px;
				.listcard-image__item{
    
    
					margin-left: 10px;
					width: 100%;
					border-radius: 5px;
					overflow: hidden;
					&:first-child{
    
    
						margin-left: 0px;
					}
					image{
    
    
						width: 100%;
						height: 100%;
					}
				}
			}
			.listcard-content__des{
    
    
				margin-top: 10px;
			}
		}
		&.mode-image{
    
    
			flex-direction: column;//垂直排列
			.listcard-image{
    
    
				width: 100%;
				height: 100px;
			}
			.listcard-content{
    
    
				padding-left: 0;
				margin-top: 10px;
				.listcard-content__des{
    
    
					display: flex;
					align-items: center;//垂直居中
					margin-top: 10px;
				}
			}
		}
	}
</style>

tabbar/index.vue

<template>
	<view class="home">
		<navbar></navbar>
		<tab :list="tabList" @tab='tab'></tab>
		<list-scroll>
			<list-card mode="base"></list-card>
			<list-card mode="column"></list-card>
			<list-card mode="image"></list-card>
		</list-scroll>
	</view>
</template>

<script>
	//uni-app有个easyCom模式,只要组件的存放位置是"@/components/组件名/组件名.vue",就可以无需引用,直接使用
	//而且还是局部引用
	// import navbar from "@/components/navbar/navbar.vue"
	export default {
    
    
		data() {
    
    
			return {
    
    
				tabList: []
			}
		},
		onLoad() {
    
    
			this.getLabel()//获取选项卡数据
		},
		methods: {
    
    
			//获取选项卡数据方法
			getLabel() {
    
    
				this.$api.get_label(
					{
    
    
						name:'get_label'
					}
				).then((res)=>{
    
    
					const {
    
    data} = res
					this.tabList = data
					console.log(res)
				})
			},
			//tab切换时候触发的方法
			tab({
    
    data,index}){
    
    
				console.log(data,index)
			}
		}
	}
</script>

<style lang="scss">
	page{
    
    
		height: 100%;
		display: flex;
	}
	.home{
    
    
		display: flex;
		flex-direction: column;//垂直排列
		flex:1;//等价于flex-grow: 1;flex-shrink: 1;flex-basis: 0%;
		overflow: hidden;//溢出隐藏
		// border: 1px red solid;
	}
</style>

在这里插入图片描述

11.实现内容切换

tabbar/index.vue

<template>
	<view class="home">
		<navbar></navbar>
		<tab :list="tabList" @tab='tab'></tab>
		<view class="home-list">
			<list :list="tabList"></list>
		</view>
	</view>
</template>

<script>
	//uni-app有个easyCom模式,只要组件的存放位置是"@/components/组件名/组件名.vue",就可以无需引用,直接使用
	//而且还是局部引用
	// import navbar from "@/components/navbar/navbar.vue"
	export default {
    
    
		data() {
    
    
			return {
    
    
				tabList: []
			}
		},
		onLoad() {
    
    
			this.getLabel() //获取选项卡数据
		},
		methods: {
    
    
			//获取选项卡数据方法
			getLabel() {
    
    
				this.$api.get_label({
    
    
					name: 'get_label'
				}).then((res) => {
    
    
					const {
    
    
						data
					} = res
					this.tabList = data
					console.log(res)
				})
			},
			//tab切换时候触发的方法
			tab({
    
    
				data,
				index
			}) {
    
    
				console.log(data, index)
			}
		}
	}
</script>

<style lang="scss">
	page {
    
    
		height: 100%;
		display: flex;
	}

	.home {
    
    
		display: flex;
		flex-direction: column; //垂直排列
		flex: 1; //等价于flex-grow: 1;flex-shrink: 1;flex-basis: 0%;
		overflow: hidden; //溢出隐藏
		.home-list{
    
    
			flex: 1;//撑开
			box-sizing: border-box;//盒内显示
		}
	}
</style>

components/list.vue

<template>
	<swiper class="home-swiper">
		<swiper-item v-for="(item,index) in list" :key="index" class="swiper-item">
			<listItem></listItem>
		</swiper-item>
	</swiper>
</template>

<script>
	import listItem from './listItem.vue' //引入list-item组件
	export default {
    
    
		//引入组件必须要注册,习惯
		components: {
    
    
			listItem
		},
		props: {
    
    
			//list是父组件传来的选项卡的数据
			list: {
    
    
				type: Array,
				default: []
			}
		},
		data() {
    
    
			return {
    
    

			};
		},

	}
</script>

<style lang="scss">
	.home-swiper {
    
    
		height: 100%;

		.swiper-item {
    
    
			height: 100%;
			overflow: hidden;

			.list-scroll {
    
    
				height: 100%;
			}
		}
	}
</style>

components/listItem.vue

<template>
	<list-scroll class="list-scroll">
		<list-card mode="base"></list-card>
		<list-card mode="base"></list-card>
		<list-card mode="base"></list-card>
		<list-card mode="base"></list-card>
		<list-card mode="base"></list-card>
		<list-card mode="base"></list-card>
	</list-scroll>
</template>

<script>
</script>

<style lang="scss">
	.list-scroll{
    
    
		height: 100%;
	}
</style>

12.选项卡与内容联动

components/list.vue

<template>
	<swiper class="home-swiper" :current="activeIndex" @change="change">
		<swiper-item v-for="(item,index) in list" :key="index" class="swiper-item">
			<listItem></listItem>
		</swiper-item>
	</swiper>
</template>

<script>
	import listItem from './listItem.vue' //引入list-item组件
	export default {
    
    
		//引入组件必须要注册,习惯
		components: {
    
    
			listItem
		},
		props: {
    
    
			//list是父组件传来的选项卡的数据
			list: {
    
    
				type: Array,
				default: []
			},
			//activeIndex是父组件传来的当前tab得位置
			activeIndex:{
    
    
				type: Number,
				default: 0
			}
		},
		data() {
    
    
			return {
    
    

			};
		},
		methods: {
    
    
			change(e) {
    
    
				const {
    
    current} = e.detail //e.detail.current是当前swiper的第几项
				this.$emit('change', current) //调用父组件传来的change方法,把current传给父组件
			}
		}

	}
</script>

<style lang="scss">
	.home-swiper {
    
    
		height: 100%;

		.swiper-item {
    
    
			height: 100%;
			overflow: hidden;

			.list-scroll {
    
    
				height: 100%;
			}
		}
	}
</style>

components/tab.vue

<template>
	<view class="tab">
		<scroll-view class="tab-scroll" scroll-x>
			<view class="tab-scroll_box">
				<view v-for="(item,index) in list" :class="{active:activeIndex===index}" :key="index" class="tab-scroll_item"
				 @click="clickTab(item,index)">
					{
    
    {
    
    item.name}}
				</view>
			</view>
		</scroll-view>
		<view class="tab-icons">
			<uni-icons type="gear" size="26" color="#666"></uni-icons>
		</view>
	</view>
</template>

<script>
	export default {
    
    
		props: {
    
    
			//接收父组件传来的一个list,类型是数组,默认值是空数组
			list: {
    
    
				type: Array,
				default: []
			},
			//接收父组件传来的一个tabIndex,类型是number,默认值是0
			tabIndex:{
    
    
				type:Number,
				default: 0,
			}
		},
		//watch事件,监听data和props里面值的变化
		watch:{
    
    
			//监听tabIndex值的变化,一旦改变,立即触发
			//方法名字与值得名字一样,接受两个参数,改变后和改变前的值
			tabIndex(newVal,oldVal){
    
    
				this.activeIndex=newVal
				console.log('newVal==>',newVal,'oldVal==>',oldVal)
			}
		},
		data() {
    
    
			return {
    
    
				activeIndex: 0
			};
		},
		methods: {
    
    
			//点击tab
			clickTab(item, index) {
    
    
				this.activeIndex = index
				this.$emit('tab', {
    
    
					data: item,
					index
				}) //调用父组件的方法,将当前tab的内容和下标传给父组件
			}
		}

	}
</script>

<style lang="scss">
	.tab {
    
    
		display: flex;
		width: 100%;
		border-bottom: 1px #f5f5f5 solid;
		background-color: #fff;
		box-sizing: border-box; //盒内元素

		.tab-scroll_box {
    
    
			display: flex;
			flex-wrap: nowrap; //不换行
			align-items: center; //垂直居中
			box-sizing: border-box; //盒内元素
			height: 45px;

			.tab-scroll_item {
    
    
				flex-shrink: 0; //不挤压
				padding: 0 10px;
				color: #333;
				font-size: 14px;

				&.active {
    
    
					color: $mk-base-color;
				}
			}
		}

		.tab-icons {
    
    
			position: relative;
			display: flex;
			justify-content: center; //水平居中
			align-items: center; //垂直居中
			width: 45px;

			&::after {
    
    
				// &::after指的是该元素后面的内容
				content: '';
				position: absolute;
				top: 12px;
				bottom: 12px;
				left: 0;
				width: 1px;
				background-color: #ddd;
			}
		}
	}
</style>

tabbar/index.vue

<template>
	<view class="home">
		<navbar></navbar>
		<tab :list="tabList" :tabIndex="tabIndex" @tab='tab'></tab>
		<view class="home-list">
			<list :list="tabList" @change="change" :activeIndex="activeIndex"></list>
		</view>
	</view>
</template>

<script>
	//uni-app有个easyCom模式,只要组件的存放位置是"@/components/组件名/组件名.vue",就可以无需引用,直接使用
	//而且还是局部引用
	// import navbar from "@/components/navbar/navbar.vue"
	export default {
    
    
		data() {
    
    
			return {
    
    
				tabList: [],//tab列表
				tabIndex:0,//当前swiper的位置
				activeIndex:0,//当前tab的位置
				
			}
		},
		onLoad() {
    
    
			this.getLabel() //获取选项卡数据
		},
		methods: {
    
    
			//获取选项卡数据方法
			getLabel() {
    
    
				this.$api.get_label({
    
    
					name: 'get_label'
				}).then((res) => {
    
    
					const {
    
    
						data
					} = res
					this.tabList = data
					console.log(res)
				})
			},
			//tab切换时候触发的方法
			tab({
    
    data,index}) {
    
    
				this.activeIndex=index //将切换后得位置赋值给activeIndex,让swiper去接收
				console.log(data, index)
			},
			//切换swiper触发的方法,获得当前swiper是第几项,再传给tab,实现tab和swiper联动
			change(current){
    
    
				this.tabIndex = current
				console.log('当前swiper的位置',current)
			}
		}
	}
</script>

<style lang="scss">
	page {
    
    
		height: 100%;
		display: flex;
	}

	.home {
    
    
		display: flex;
		flex-direction: column; //垂直排列
		flex: 1; //等价于flex-grow: 1;flex-shrink: 1;flex-basis: 0%;
		overflow: hidden; //溢出隐藏
		.home-list{
    
    
			flex: 1;//撑开
			box-sizing: border-box;//盒内显示
		}
	}
</style>

13.内容卡片数据初始化

cloudfunctions-aliyun/get_list.js

'use strict';
const db = uniCloud.database()//创建数据库的引用
exports.main = async (event, context) => {
    
    
	
	let list = await db.collection('article')
	.field({
    
    content:false})//field()指定返回字段,不要content;false表示不返回,true表示只返回
	.get()//获取article表数据
	
	
	//返回数据给客户端
	return {
    
    
		code:200,
		msg:'数据请求成功',
		data:list.data
	}
};

common/api/list.js

/* 存放所有接口请求的api */
import $http from '../http.js'
export const get_label = (data) => {
    
    
	return $http({
    
    
		url:'get_label',
		data
	})
}
export const get_list = (data) => {
    
    
	return $http({
    
    
		url:'get_list',
		data
	})
}

components/list/list.vue

<template>
	<swiper class="home-swiper" :current="activeIndex" @change="change">
		<swiper-item v-for="(item,index) in list" :key="index" class="swiper-item">
			<listItem :articleList="articleList"></listItem>
		</swiper-item>
	</swiper>
</template>

<script>
	import listItem from './listItem.vue' //引入list-item组件
	export default {
    
    
		//引入组件必须要注册,习惯
		components: {
    
    
			listItem
		},
		props: {
    
    
			//list是父组件传来的选项卡的数据
			list: {
    
    
				type: Array,
				default: []
			},
			//activeIndex是父组件传来的当前tab得位置
			activeIndex:{
    
    
				type: Number,
				default: 0
			}
		},
		data() {
    
    
			return {
    
    
				articleList:[]//文章列表,需要传给子组件
			};
		},
		created() {
    
    
			this.getList()//调用get_list接口
		},
		methods: {
    
    
			change(e) {
    
    
				const {
    
    current} = e.detail //e.detail.current是当前swiper的第几项
				this.$emit('change', current) //调用父组件传来的change方法,把current传给父组件
			},
			//调用get_list接口的方法
			getList(){
    
    
				this.$api.get_list().then(res=>{
    
    
					const {
    
    data} = res
					this.articleList=data
					console.log(res)
				})
			}
		}

	}
</script>

<style lang="scss">
	.home-swiper {
    
    
		height: 100%;

		.swiper-item {
    
    
			height: 100%;
			overflow: hidden;

			.list-scroll {
    
    
				height: 100%;
			}
		}
	}
</style>

components/list/listItem.vue

<template>
	<list-scroll class="list-scroll">
		<list-card mode="base" v-for="item in articleList" :key="item._id" :item="item"></list-card>
	</list-scroll>
</template>

<script>
	export default {
    
    
		props: {
    
    
			//articleList是父组件传来的文章列表
			articleList:{
    
    
				type:Array,
				default:[]
			}
		}
	}
</script>

<style lang="scss">
	.list-scroll {
    
    
		height: 100%;
	}
</style>

components/list-card.vue

<template>
	<view>
		<!-- 基础卡片 -->
		<view v-if="item.mode==='base'" class="listcard">
			<view class="listcard-image">
				<image :src="item.cover[0]" mode="aspectFill"></image>
			</view>
			<view class="listcard-content">
				<view class="listcard-content__title">
					<text>{
    
    {
    
    item.title}}</text>
				</view>
				<view class="listcard-content__des">
					<view class="listcard-content__des-label">
						<view class="listcard-content__des-label-item">{
    
    {
    
    item.classify}}</view>
					</view>
					<view class="listcard-content__des-browse">{
    
    {
    
    item.browse_count}}浏览</view>
				</view>
			</view>
		</view>
		<!-- 多图模式卡片 -->
		<view v-if="item.mode==='column'" class="listcard mode-column">
			<view class="listcard-content">
				<view class="listcard-content__title">
					<text>{
    
    {
    
    item.title}}</text>
				</view>
				<view class="listcard-image">
					<view v-if="index < 3" v-for="(item,index) in item.cover" :key="index" class="listcard-image__item">
						<image :src="item" mode="aspectFill"></image>
					</view>
				</view>
				<view class="listcard-content__des">
					<view class="listcard-content__des-label">
						<view class="listcard-content__des-label-item">{
    
    {
    
    item.classify}}</view>
					</view>
					<view class="listcard-content__des-browse">{
    
    {
    
    item.browse_count}}浏览</view>
				</view>
			</view>
		</view>
		<!-- 大图模式卡片 -->
		<view v-if="item.mode==='image'" class="listcard mode-image">
			<view class="listcard-image">
					<image :src="item.cover[0]" mode="aspectFill"></image>
			</view>
			<view class="listcard-content">
				<view class="listcard-content__title">
					<text>{
    
    {
    
    item.title}}</text>
				</view>
				<view class="listcard-content__des">
					<view class="listcard-content__des-label">
						<view class="listcard-content__des-label-item">{
    
    {
    
    item.classify}}</view>
					</view>
					<view class="listcard-content__des-browse">{
    
    {
    
    item.browse_count}}浏览</view>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	export default {
    
    
		props:{
    
    
			//通过接受mode来判断,显示什么模式的卡片,mode是String类型的字符串,默认值是base
			//base基础卡片,column多图卡片,image,大图卡片
			// mode:{
    
    
			// 	type:String,
			// 	default:'base'
			// },
			//item是父组件传来的文章列表里面的信息
			item:{
    
    
				type:Object,
				default:{
    
    },
			}
		},
		data() {
    
    
			return {
    
    

			};
		}
	}
</script>

<style lang="scss">
	.listcard {
    
    
		display: flex;
		padding: 10px;
		margin: 10px;
		border-radius: 5px;
		box-shadow: 0 0 5px 1px rgba($color:#000000, $alpha:0.1);
		//5px模糊效果,1px扩散
		box-sizing: border-box;

		.listcard-image {
    
    
			flex-shrink: 0; //防止挤压,正常显示
			width: 60px;
			height: 60px;
			border-radius: 5px;
			overflow: hidden;

			image {
    
    
				//使图片全部正常显示
				width: 100%;
				height: 100%;
			}
		}

		.listcard-content {
    
    
			display: flex;
			flex-direction: column; //垂直排列
			justify-content: space-between; //垂直上下对齐
			padding-left: 10px;
			width: 100%;

			.listcard-content__title {
    
    
				font-size: 14px;
				color: #333;
				font-weight: 400;
				line-height: 1.2;

				text {
    
    
					overflow: hidden; //溢出隐藏
					text-overflow: ellipsis; //当文本溢出包含元素省略符号显示
					display: -webkit-box; // 将对象作为弹性伸缩盒子模型显示
					-webkit-line-clamp: 2; //只显示两行
					-webkit-box-orient: vertical; // 从上到下垂直排列子元素
				}
			}

			.listcard-content__des {
    
    
				display: flex;
				justify-content: space-between; //左右两边对齐
				font-size: 12px;

				.listcard-content__des-label {
    
    
					display: flex;

					.listcard-content__des-label-item {
    
    
						padding: 0 5px;
						margin-right: 5px;
						border-radius: 15px;
						color: $mk-base-color;
						border: 1px $mk-base-color solid;
					}
				}

				.listcard-content__des-browse {
    
    
					color: #999;
					line-height: 1.5;
				}
			}
		}
		&.mode-column{
    
    
			.listcard-content{
    
    
				width: 100%;
				padding-left: 0px;
			}
			.listcard-image{
    
    
				display: flex;
				margin-top: 10px;
				width: 100%;
				height: 70px;
				.listcard-image__item{
    
    
					margin-left: 10px;
					width: 100%;
					border-radius: 5px;
					overflow: hidden;
					&:first-child{
    
    
						margin-left: 0px;
					}
					image{
    
    
						width: 100%;
						height: 100%;
					}
				}
			}
			.listcard-content__des{
    
    
				margin-top: 10px;
			}
		}
		&.mode-image{
    
    
			flex-direction: column;//垂直排列
			.listcard-image{
    
    
				width: 100%;
				height: 100px;
			}
			.listcard-content{
    
    
				padding-left: 0;
				margin-top: 10px;
				.listcard-content__des{
    
    
					display: flex;
					align-items: center;//垂直居中
					margin-top: 10px;
				}
			}
		}
	}
</style>

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44745920/article/details/110846641
今日推荐