注:前言、目录见 https://god-excious.blog.csdn.net/article/details/105312456
官方文档 https://uniapp.dcloud.io/collocation/pages?id=配置项列表
官方文档 https://uniapp.dcloud.io/collocation/pages?id=style
可以在page-json中配置一些样式,包括“导航栏”、“按钮”、“下拉刷新”等样式。
大多数内容可以到官方文档中找到,下面给出一种配置的样式
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
// "navigationBarTitleText": "仿糗事百科"
"app-plus": {
// 隐藏滚动条
"scrollIndicator":"none",
// 配置导航栏
"titleNView": {
// 配置搜索框
"searchInput": {
// 对齐方式(默认值center)
"align": "center",
// 背景色
"backgroundColor": "#F7F7F7",
// 边框圆角
"borderRadius": "4px",
// 提示文字
"placeholder": "搜索糗事",
// 阻止输入(一般都是阻止输入,点击后跳转到搜索页面)
"disabled": "true"
},
// 配置按钮
"buttons": [
// 左边按钮
{
// 设置颜色
"color": "#FF9619",
// 设置按钮按下的颜色
"colorPressed": "#BBBBBB",
// 设置浮动
"float": "left",
// 设置按钮上文字大小
"fontSize": "22px",
// 设置字体文件的路径
"fontSrc": "/static/font/icon.ttf",
// 设置按钮上显示的文字
"text": "\ue609"
},
// 右边按钮
{
"color": "#000000",
"colorPressed": "#BBBBBB",
"float": "right",
"fontSize": "22px",
"fontSrc": "/static/font/icon.ttf",
"text": "\ue653"
}
]
}
}
}
}
,{
"path" : "pages/news/news",
"style" : {}
}
,{
"path" : "pages/paper/paper",
"style" : {}
}
,{
"path" : "pages/home/home",
"style" : {}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "仿糗事百科",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color":"#adadad", // 导航栏文字默认颜色
"selectedColor":"#fee42a", // 导航栏选中颜色
"backgroundColor":"#fff", // 导航栏背景颜色
"borderStyle":"white", // 导航栏边框样式
"list":[ // tab的列表
{
"pagePath":"pages/index/index", // 导航栏对应页面路径(不用写.vue)
"text":"糗事", // 导航栏文字
"iconPath":"static/tabbar/index.png", // 导航栏未选中的图标路径
"selectedIconPath":"static/tabbar/indexed.png" // 导航栏已选中的图标路径
},
{
"pagePath":"pages/news/news",
"text":"动态",
"iconPath":"static/tabbar/news.png",
"selectedIconPath":"static/tabbar/newsed.png"
},
{
"pagePath":"pages/paper/paper",
"text":"小纸条",
"iconPath":"static/tabbar/paper.png",
"selectedIconPath":"static/tabbar/papered.png"
},
{
"pagePath":"pages/home/home",
"text":"我的",
"iconPath":"static/tabbar/home.png",
"selectedIconPath":"static/tabbar/homed.png"
}
]
}
}
注:
buttons
中字体的路径fontSrc
对应了图标项目中的iconfont.ttf文件的路径,一般将其引入到uni-app项目中的/static/font
文件夹下buttons
中提示文字text
,填写的内容为图标项目中demo_index.html打开后Unicode下,该图标的编号。例如:某图标在Unicode中编号为
,就应该填写"text": "\ue601"
,以此类推。图片组件——官方文档 https://uniapp.dcloud.io/component/image
格式大致如下
<image src="../../static/demo/userpic/12.jpg" mode="widthFix" lazy-load>image>
属性 | 功能 |
---|---|
src |
图片路径 |
mode |
图片裁剪、缩放的模式(widthFix 代表宽度不变,高度自动变化,保持原图宽高比不变) |
lazy-load |
图片懒加载 |
在设计时,需要注意以下一些细节
class="网页名-list-i"
的view
组件,然后将它们放到一个class="网页名-list"
的view
组件中view
组件下,然后以flex布局(默认主轴方向即可)设置交叉轴(纵向)居中——align-item: center;
**,即可在同一行上垂直居中排版margin-right
,如果有多个图标和文字的组,并且排列在同一侧,可以给图标的父标签设置一个margin-right
**,让组与组之间保持间距。网页名-list-i
的元素设置一个padding-top
,最后一个行元素可以考虑设置一个padding: ??px, 0;
让上下都保持一部分间距padding
,显示出这样的空隙。border-bottom
属性实现u-f
、u-f-ac
、u-f-ajc
、u-f-jsb
这样的类名,然后给需要的标签加上这些类名。index.css文件
<template>
<view>
<view class="index-list">
<view class="index-list1 u-f-ac u-f-jsb">
<view class="u-f-ac">
<image src="../../static/demo/userpic/12.jpg" mode="widthFix" lazy-load>image>
昵称
view>
<view class="u-f-ac">
<view class="icon iconfont icon-zengjia">view>关注
view>
view>
<view class="index-list2">这是标题view>
<view class="index-list3">
<image src="../../static/demo/datapic/11.jpg" mode="widthFix" lazy-load>image>
view>
<view class="index-list4 u-f-ac u-f-jsb">
<view class="u-f-ac">
<view class="u-f-ac">
<view class="icon iconfont icon-icon_xiaolian-mian u-f-ac">view>
10
view>
<view class="u-f-ac">
<view class="icon iconfont icon-kulian u-f-ac">view>
10
view>
view>
<view class="u-f-ac">
<view class="u-f-ac">
<view class="icon iconfont icon-pinglun1 u-f-ac">view>
10
view>
<view class="u-f-ac">
<view class="icon iconfont icon-zhuanfa u-f-ac">view>
10
view>
view>
view>
view>
view>
template>
<script>
export default {
data() {
return {
title: 'Hello'
}
},
onLoad() {
},
methods: {
}
}
script>
<style>
/* common.css在App.vue文件中引入了进来 */
.index-list {
padding: 20upx;
border-bottom: 1upx solid #EEEEEE;
}
.index-list1>view:first-child {
color: #999999;
}
.index-list1>view:first-child image {
width: 90upx;
height: 90upx;
border-radius: 50%;
margin-right: 10upx;
}
.index-list1>view:last-child {
background-color: #F4F4F4;
border-radius: 5upx;
padding: 0 10upx;
}
.index-list2 {
padding-top: 15upx;
font-size: 32upx;
}
.index-list3 {
padding-top: 15upx;
}
.index-list3>image {
width: 100%;
border-radius: 20upx;
}
.index-list4 {
padding: 15upx 0;
}
.index-list4>view {
color: #999999;
}
.index-list4>view>view:first-child, .index-list4>view>view>view {
margin-right: 10upx;
}
style>
common.css文件
/* 代表flex布局 */
.u-f, .u-f-ac, .u-f-ajc, .u-f-jsb {
display: flex;
}
.u-f-ac, .u-f-ajc {
align-items: center;
}
.u-f-ajc {
justify-content: center;
}
.u-f-jsb {
justify-content: space-between;
}
App.vue文件
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
script>
<style>
/*每个页面公共css */
@import './common/uni.css';
@import './common/icon.css';
@import './common/animate.css';
@import './common/common.css';
style>
pages.json文件
和上一节一样
效果图
在设计时,有以下一些细节
postion: relative;
,对子组件用postion: absolute;
。然后如果需要显示在父组件的正中央,可以给父组件设置一个class="u-f-ajc"
;如果需要显示在父组件的边上,可以设置right
、left
、top
、bottom
其中的不同方向上的两个。color
,然后点击对应的颜色,拖动透明度条,进行调节,如果需要rgba
,点击上下切换的小箭头切换,然后把color
属性里的rgba
值复制过来。font-size
属性即可。index.vue文件
和上一节课相比,主要增加、改动的地方是17~21行、94行、115~130行
<template>
<view>
<view class="index-list">
<view class="index-list1 u-f-ac u-f-jsb">
<view class="u-f-ac">
<image src="../../static/demo/userpic/12.jpg" mode="widthFix" lazy-load>image>
昵称
view>
<view class="u-f-ac">
<view class="icon iconfont icon-zengjia">view>关注
view>
view>
<view class="index-list2">这是标题view>
<view class="index-list3 u-f-ajc">
<image src="../../static/demo/datapic/11.jpg" mode="widthFix" lazy-load>image>
<view class="index-list-play icon iconfont icon-bofang">view>
<view class="index-list-playinfo">
20w次播放 2:17
view>
view>
<view class="index-list4 u-f-ac u-f-jsb">
<view class="u-f-ac">
<view class="u-f-ac">
<view class="icon iconfont icon-icon_xiaolian-mian u-f-ac">view>
10
view>
<view class="u-f-ac">
<view class="icon iconfont icon-kulian u-f-ac">view>
10
view>
view>
<view class="u-f-ac">
<view class="u-f-ac">
<view class="icon iconfont icon-pinglun1 u-f-ac">view>
10
view>
<view class="u-f-ac">
<view class="icon iconfont icon-zhuanfa u-f-ac">view>
10
view>
view>
view>
view>
view>
template>
<script>
export default {
data() {
return {
title: 'Hello'
}
},
onLoad() {
},
methods: {
}
}
script>
<style>
.index-list {
padding: 20upx;
border-bottom: 1upx solid #EEEEEE;
}
.index-list1>view:first-child {
color: #999999;
}
.index-list1>view:first-child image {
width: 90upx;
height: 90upx;
border-radius: 50%;
margin-right: 10upx;
}
.index-list1>view:last-child {
background-color: #F4F4F4;
border-radius: 5upx;
padding: 0 10upx;
}
.index-list2 {
padding-top: 15upx;
font-size: 32upx;
}
.index-list3 {
position: relative;
padding-top: 15upx;
}
.index-list3>image {
width: 100%;
border-radius: 20upx;
}
.index-list4 {
padding: 15upx 0;
}
.index-list4>view {
color: #999999;
}
.index-list4>view>view:first-child, .index-list4>view>view>view {
margin-right: 10upx;
}
.index-list-play {
position: absolute;
font-size: 140upx;
color: #FFFFFF;
}
.index-list-playinfo {
position: absolute;
background-color: rgba(51, 51, 51, 0.62);
color: #FFFFFF;
bottom: 8upx;
right: 8upx;
border-radius: 40upx;
font-size: 22upx;
padding: 0 12upx;
}
style>
其他文件
和上一小节相同,不再重复
效果图
在script
脚本的data()
中,用数组将页面中出现的数据封装成一个个对象
<script>
export default {
data() {
return {
list:[
{
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
userpic: "../../static/demo/userpic/12.jpg",
username: "昵称",
isguanzhu: true,
title: "这是标题",
type: "video",
titlepic: "../../static/demo/datapic/11.jpg",
// 播放次数
playnum: "2w",
long: "2:47",
infonum: {
index: 2,
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
]
}
},
onLoad() {
},
methods: {
}
}
script>
因为有可能存在多个对象,所以需要列表渲染(循环),详细代码见下面一小段。
“是否关注”这一状态可以完全放到list
对象中一个对象的一个布尔变量来标识,用v-show
进行条件渲染。
“是否点赞、是否点踩”这些状态,和点赞数、点踩数一起放到list
对象中一个对象的一个infonum
里,状态用index
来标识,值为0
代表“未操作”,值为1
代表“点了赞”,值为2
代表“点了踩”。然后在:class
属性中根据一个条件表达式选择判断是否获得点击后的类名active
的样式。
<template>
<view>
<block v-for="(item, index) in list" :key="index">
<view class="index-list">
<view class="index-list1 u-f-ac u-f-jsb">
<view class="u-f-ac">
<image :src="item.userpic" mode="widthFix" lazy-load>image>
{{item.username}}
view>
<view class="u-f-ac" v-show="item.isguanzhu">
<view class="icon iconfont icon-zengjia">view>关注
view>
view>
<view class="index-list2">{{item.title}}view>
<view class="index-list3 u-f-ajc">
<image :src="item.titlepic" mode="widthFix" lazy-load>image>
<template v-if="item.type=='video'">
<view class="index-list-play icon iconfont icon-bofang">view>
<view class="index-list-playinfo">
{{item.playnum}}次播放 {{item.long}}
view>
template>
view>
<view class="index-list4 u-f-ac u-f-jsb">
<view class="u-f-ac">
<view class="u-f-ac" :class="{'active':(item.infonum.index==1)}">
<view class="icon iconfont icon-icon_xiaolian-mian u-f-ac">view>
{{item.infonum.dingnum}}
view>
<view class="u-f-ac" :class="{'active':(item.infonum.index==2)}">
<view class="icon iconfont icon-kulian u-f-ac">view>
{{item.infonum.cainum}}
view>
view>
<view class="u-f-ac">
<view class="u-f-ac">
<view class="icon iconfont icon-pinglun1 u-f-ac">view>
{{item.commentnum}}
view>
<view class="u-f-ac">
<view class="icon iconfont icon-zhuanfa u-f-ac">view>
{{item.sharenum}}
view>
view>
view>
view>
block>
view>
template>
<script>
export default {
data() {
return {
list:[
{
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
userpic: "../../static/demo/userpic/12.jpg",
username: "昵称",
isguanzhu: true,
title: "这是标题",
type: "video",
titlepic: "../../static/demo/datapic/11.jpg",
// 播放次数
playnum: "2w",
long: "2:47",
infonum: {
index: 2,
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
]
}
},
onLoad() {
},
methods: {
}
}
script>
<style>
.index-list {
padding: 20upx;
border-bottom: 1upx solid #EEEEEE;
}
.index-list1>view:first-child {
color: #999999;
}
.index-list1>view:first-child image {
width: 90upx;
height: 90upx;
border-radius: 50%;
margin-right: 10upx;
}
.index-list1>view:last-child {
background-color: #F4F4F4;
border-radius: 5upx;
padding: 0 10upx;
}
.index-list2 {
padding-top: 15upx;
font-size: 32upx;
}
.index-list3 {
position: relative;
padding-top: 15upx;
}
.index-list3>image {
width: 100%;
border-radius: 20upx;
}
.index-list4 {
padding: 15upx 0;
}
.index-list4>view {
color: #999999;
}
.index-list4>view>view:first-child, .index-list4>view>view>view {
margin-right: 10upx;
}
.index-list-play {
position: absolute;
font-size: 140upx;
color: #FFFFFF;
}
.index-list-playinfo {
position: absolute;
background-color: rgba(51, 51, 51, 0.62);
color: #FFFFFF;
bottom: 8upx;
right: 8upx;
border-radius: 40upx;
font-size: 22upx;
padding: 0 12upx;
}
.index-list4 .active, .index-list4 .active > view{
color: #C5F61C;
}
style>
效果图
像是上面我们做好的一个list样式,就可以封装起来。
封装的步骤如下:
创建好一个components文件夹,然后右键新建组件,在components文件夹下再创建一个index文件夹,把之前新建好的组件文件index-list.vue文件放到index文件夹下
将index.vue文件中block标签下的所有标签剪切,放到index-list.vue文件中对应位置
将index.vue文件中 style
下的所有样式剪切,放到index-list.vue文件中对应位置。
注:最好给里面的style
加上一个属性scoped
,避免在引入组件样式后污染原本设置好的其他样式
在index-list.vue文件的script
下的props
中,定义需要传入的变量及其类型,像是这里就需要将循环中的item
和index
传进来,形式为变量名: 类型
在index.vue文件的script
的export default
前,将组件文件引入进来,形如:
import indexList from "../../components/index/index-list.vue";
注:将中划线命名改写为小驼峰命名
在index.vue文件的script
的components
中,注册相应的组件
以标签形式将组件进行引入,在属性中传入需要的参数,形如:
<block v-for="(item, index) in list" :key="index">
<index-list :item="item" :index="index">index-list>
block>
封装后的项目代码文件如下:
index.vue文件
<template>
<view>
<block v-for="(item, index) in list" :key="index">
<index-list :item="item" :index="index">index-list>
block>
view>
template>
<script>
import indexList from "../../components/index/index-list.vue";
export default {
components: {
indexList
},
data() {
return {
list:[
{
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
userpic: "../../static/demo/userpic/12.jpg",
username: "昵称",
isguanzhu: true,
title: "这是标题",
type: "video",
titlepic: "../../static/demo/datapic/11.jpg",
// 播放次数
playnum: "2w",
long: "2:47",
infonum: {
index: 2,
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
]
}
},
onLoad() {
},
methods: {
}
}
script>
<style>
style>
index-list.vue文件
<template>
<view class="index-list">
<view class="index-list1 u-f-ac u-f-jsb">
<view class="u-f-ac">
<image :src="item.userpic" mode="widthFix" lazy-load>image>
{{item.username}}
view>
<view class="u-f-ac" v-show="item.isguanzhu">
<view class="icon iconfont icon-zengjia">view>关注
view>
view>
<view class="index-list2">{{item.title}}view>
<view class="index-list3 u-f-ajc">
<image :src="item.titlepic" mode="widthFix" lazy-load>image>
<template v-if="item.type=='video'">
<view class="index-list-play icon iconfont icon-bofang">view>
<view class="index-list-playinfo">
{{item.playnum}}次播放 {{item.long}}
view>
template>
view>
<view class="index-list4 u-f-ac u-f-jsb">
<view class="u-f-ac">
<view class="u-f-ac" :class="{'active':(item.infonum.index==1)}">
<view class="icon iconfont icon-icon_xiaolian-mian u-f-ac">view>
{{item.infonum.dingnum}}
view>
<view class="u-f-ac" :class="{'active':(item.infonum.index==2)}">
<view class="icon iconfont icon-kulian u-f-ac">view>
{{item.infonum.cainum}}
view>
view>
<view class="u-f-ac">
<view class="u-f-ac">
<view class="icon iconfont icon-pinglun1 u-f-ac">view>
{{item.commentnum}}
view>
<view class="u-f-ac">
<view class="icon iconfont icon-zhuanfa u-f-ac">view>
{{item.sharenum}}
view>
view>
view>
view>
template>
<script>
export default {
props: {
item: Object,
index: Number
}
}
script>
<style scoped>
.index-list {
padding: 20upx;
border-bottom: 1upx solid #EEEEEE;
}
.index-list1>view:first-child {
color: #999999;
}
.index-list1>view:first-child image {
width: 90upx;
height: 90upx;
border-radius: 50%;
margin-right: 10upx;
}
.index-list1>view:last-child {
background-color: #F4F4F4;
border-radius: 5upx;
padding: 0 10upx;
}
.index-list2 {
padding-top: 15upx;
font-size: 32upx;
}
.index-list3 {
position: relative;
padding-top: 15upx;
}
.index-list3>image {
width: 100%;
border-radius: 20upx;
}
.index-list4 {
padding: 15upx 0;
}
.index-list4>view {
color: #999999;
}
.index-list4>view>view:first-child, .index-list4>view>view>view {
margin-right: 10upx;
}
.index-list-play {
position: absolute;
font-size: 140upx;
color: #FFFFFF;
}
.index-list-playinfo {
position: absolute;
background-color: rgba(51, 51, 51, 0.62);
color: #FFFFFF;
bottom: 8upx;
right: 8upx;
border-radius: 40upx;
font-size: 22upx;
padding: 0 12upx;
}
.index-list4 .active, .index-list4 .active > view{
color: #C5F61C;
}
style>
其他文件
和之前基本一样
效果图
和上一段展示的效果图一样
根据uni-app-hello项目中的tabbar.vue文件,依次进行以下操作:
{name: "??", id: "??"}
的形式,写到data
中的数组tabBars
下。class="uni-tab-bar"
的view
组件view
组件下创建一个class="uni-swiper-tab" scroll-x
的scroll-view
组件scroll-view
组件下创建一个block
组件,属性为v-for="(tab,index) in tabBars" :key="tab.id"
,列表渲染class="swiper-tab-list"
的view
组件view
组件内容显示{{tab.name}}
有如下注意点:
view
组件设置:class="{'active': tabIndex==index}"
data
中设置tabIndex
值(先设置为0代表初始选中第一个),然后在methods
中设置一个处理函数tabtap(index)
,内容就是this.tabIndex=index;
,最后放到被列表渲染的view
组件上用@tap="tabtap(index)"
绑定事件处理.uni-swiper-tab
的scroll-view
组件设置border-bottom
属性,调整导航栏分隔线的样式.swiper-tab-list
的列表渲染view
组件设置文字的样式.uni-tab-bar
下的.active
的列表渲染view
组件设置被选中后的文字样式view
组件下再加一个class="swiper-tab-line"
的view
组件,然后对.active
下的.swiper-tab-line
设置类似border-botom
、margin: 0 auto;
、border-radius
等等样式以下仅展示index.vue文件的代码
<template>
<view>
<view class="uni-tab-bar">
<scroll-view scroll-x class="uni-swiper-tab">
<block v-for="(tab, index) in tabBars" :key="tab.id">
<view class="swiper-tab-list" :class="{'active': tabIndex==index}" @tap="tabtap(index)">
{{tab.name}}
<view class="swiper-tab-line">view>
view>
block>
scroll-view>
view>
view>
template>
<script>
import indexList from "../../components/index/index-list.vue";
export default {
components: {
indexList
},
data() {
return {
tabIndex: 0,
tabBars: [
{name: "关注", id: "guanzhu"},
{name: "推荐", id: "tuijian"},
{name: "体育", id: "tiyu"},
{name: "热点", id: "redian"},
{name: "财经", id: "caijing"},
{name: "娱乐", id: "yule"}
],
list:[
{
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
userpic: "../../static/demo/userpic/12.jpg",
username: "昵称",
isguanzhu: true,
title: "这是标题",
type: "video",
titlepic: "../../static/demo/datapic/11.jpg",
// 播放次数
playnum: "2w",
long: "2:47",
infonum: {
index: 2,
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
]
}
},
onLoad() {
},
methods: {
tabtap(index){
this.tabIndex=index;
}
}
}
script>
<style>
.swiper-tab-list {
color: #969696;
font-weight: bold;
}
.uni-swiper-tab {
border-bottom: 1upx solid #EEEEEE;
}
.uni-tab-bar .active {
color: #343434;
}
/* 被选中才出现下划线 */
.active .swiper-tab-line {
border-bottom: 6upx solid #FEDE33;
width: 70upx;
margin: 0 auto;
border-top: 6upx solid #FEDE33;
border-radius: 20upx;
}
style>
效果图
swiper
组件·官方文档 https://uniapp.dcloud.io/component/swiper
获取系统信息·官方文档 https://uniapp.dcloud.io/api/system/info
newslist
,里面是6个list
对象,分别与滚动栏上的6个种类对应class="uni-tab-bar"
的view
组件中使用了class="swiper-box"
的swiper
组件,在swiper
组件下用swiper-item
组件对数组newslist
进行列表渲染,然后在swiper-item
组件下用scroll-view
组件创建滚动条,滚动条内将之前的内容block
组件放入,大致就完成了script
下的onLoad()
中用uni.getSystemInfo({ success:(res)=> { } })
在函数体中用res.windowHeight
获取显示高度(其他属性见上面给出的官方文档)swiperheight
存储大小对应的px
值,然后给swiper
组件设置相应尺寸的style
(因为upx
不支持在style
中动态绑定)。swiperheight
的计算应该放到onLoad()
中的uni.getSystemInfo
里,然后把结果传入这个变量值中。由于在uin.css文件中,.uni-tab-bar
、.swiper-box
的height
被计算为calc(100% - 100upx)
,我们可以用类似的方法,计算出this.swiperheight=res.windowHeight-uni.upx2px(100);
,这样的高度是差不多就是可以自适应的。swiper
组件设置:current:"tabIndex";
可以让横向滚动时,显示出对应的列表内容。swiper
组件设置@change="tabChange"
,然后定义好函数tabChange
,里面有默认参数event
(调用时不用加上括号和参数列表),函数的内容定义this.tabIndex=e.detail.current;
,这样可以设置横向滚动时,让导航栏也一起随之变化<template>
<view>
<view class="uni-tab-bar">
<scroll-view scroll-x class="uni-swiper-tab">
<block v-for="(tab, index) in tabBars" :key="tab.id">
<view class="swiper-tab-list" :class="{'active': tabIndex==index}" @tap="tabtap(index)">
{{tab.name}}
<view class="swiper-tab-line">view>
view>
block>
scroll-view>
view>
<view class="uni-tab-bar">
<swiper class="swiper-box" :style="{height: swiperheight+'px'}" :current="tabIndex" @change="tabChange">
<swiper-item v-for="(items, index) in newslist" :key="index">
<scroll-view scroll-y class="list">
<block v-for="(item, index1) in items.list" :key="index1">
<index-list :item="item" :index="index1">index-list>
block>
scroll-view>
swiper-item>
swiper>
view>
view>
template>
<script>
import indexList from "../../components/index/index-list.vue";
export default {
components: {
indexList
},
data() {
return {
swiperheight: 500,
tabIndex: 0,
tabBars: [
{name: "关注", id: "guanzhu"},
{name: "推荐", id: "tuijian"},
{name: "体育", id: "tiyu"},
{name: "热点", id: "redian"},
{name: "财经", id: "caijing"},
{name: "娱乐", id: "yule"}
],
newslist: [
{
list:[
{
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
userpic: "../../static/demo/userpic/12.jpg",
username: "昵称",
isguanzhu: true,
title: "这是标题",
type: "video",
titlepic: "../../static/demo/datapic/11.jpg",
// 播放次数
playnum: "2w",
long: "2:47",
infonum: {
index: 2,
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
]
},
{
list:[
{
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
userpic: "../../static/demo/userpic/12.jpg",
username: "昵称",
isguanzhu: true,
title: "这是标题",
type: "video",
titlepic: "../../static/demo/datapic/11.jpg",
// 播放次数
playnum: "2w",
long: "2:47",
infonum: {
index: 2,
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
userpic: "../../static/demo/userpic/12.jpg",
username: "昵称",
isguanzhu: true,
title: "这是标题",
type: "video",
titlepic: "../../static/demo/datapic/11.jpg",
// 播放次数
playnum: "2w",
long: "2:47",
infonum: {
index: 2,
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
]
},
{
list:[
{
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
userpic: "../../static/demo/userpic/12.jpg",
username: "昵称",
isguanzhu: true,
title: "这是标题",
type: "video",
titlepic: "../../static/demo/datapic/11.jpg",
// 播放次数
playnum: "2w",
long: "2:47",
infonum: {
index: 2,
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
]
},
{
list:[]
},
{
list:[]
},
{
list:[]
}
]
}
},
onLoad() {
uni.getSystemInfo({
success:(res)=> {
let height = res.windowHeight-uni.upx2px(100);
this.swiperheight=height;
}
});
},
methods: {
// tabBar点击事件
tabtap(index) {
this.tabIndex=index;
},
// 滑动联动切换导航栏
tabChange(e) {
// console.log(JSON.stringify(e.detail));
this.tabIndex=e.detail.current;
}
}
}
script>
<style>
.swiper-tab-list {
color: #969696;
font-weight: bold;
}
.uni-swiper-tab {
border-bottom: 1upx solid #EEEEEE;
}
.uni-tab-bar .active {
color: #343434;
}
/* 被选中才出现下划线 */
.active .swiper-tab-line {
border-bottom: 6upx solid #FEDE33;
width: 70upx;
margin: 0 auto;
border-top: 6upx solid #FEDE33;
border-radius: 20upx;
}
style>
步骤如下:
props
中定义好要使用到的变量,在index.vue文件中用自定义的swiper-tab-head
组件将导航栏引入,然后以:tabBars="tabBars" :tabIndex="tabIndex"
的方式传入相关变量this.$emit('tabtap', index);
,意思是监听一个自定义的名为'tabtap'
的事件、然后把index
传过去,然后在父组件里通过设置属性@tabtap="tabtap"
,意思是当触发了tabtap
事件后、将子组件中通过tabtap
事件传入的index
作为参数、传入父组件的函数tabtap
中、进行事件处理,从而达到父子组件通信的效果。index.vue文件
<template>
<view>
<swiper-tab-head :tabBars="tabBars" :tabIndex="tabIndex" @tabtap="tabtap">swiper-tab-head>
<view class="uni-tab-bar">
<swiper class="swiper-box" :style="{height: swiperheight+'px'}" :current="tabIndex" @change="tabChange">
<swiper-item v-for="(items, index) in newslist" :key="index">
<scroll-view scroll-y class="list">
<block v-for="(item, index1) in items.list" :key="index1">
<index-list :item="item" :index="index1">index-list>
block>
scroll-view>
swiper-item>
swiper>
view>
view>
template>
<script>
import indexList from "../../components/index/index-list.vue";
import swiperTabHead from "../../components/index/swiper-tab-head.vue";
export default {
// 注册组件
components: {
indexList,
swiperTabHead
},
data() {
return {
swiperheight: 500,
tabIndex: 0,
tabBars: [
{name: "关注", id: "guanzhu"},
{name: "推荐", id: "tuijian"},
{name: "体育", id: "tiyu"},
{name: "热点", id: "redian"},
{name: "财经", id: "caijing"},
{name: "娱乐", id: "yule"}
],
newslist: [
{
list:[
{
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
userpic: "../../static/demo/userpic/12.jpg",
username: "昵称",
isguanzhu: true,
title: "这是标题",
type: "video",
titlepic: "../../static/demo/datapic/11.jpg",
// 播放次数
playnum: "2w",
long: "2:47",
infonum: {
index: 2,
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
]
},
{
list:[
{
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
userpic: "../../static/demo/userpic/12.jpg",
username: "昵称",
isguanzhu: true,
title: "这是标题",
type: "video",
titlepic: "../../static/demo/datapic/11.jpg",
// 播放次数
playnum: "2w",
long: "2:47",
infonum: {
index: 2,
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
userpic: "../../static/demo/userpic/12.jpg",
username: "昵称",
isguanzhu: true,
title: "这是标题",
type: "video",
titlepic: "../../static/demo/datapic/11.jpg",
// 播放次数
playnum: "2w",
long: "2:47",
infonum: {
index: 2,
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
]
},
{
list:[
{
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
{
userpic: "../../static/demo/userpic/12.jpg",
username: "昵称",
isguanzhu: true,
title: "这是标题",
type: "video",
titlepic: "../../static/demo/datapic/11.jpg",
// 播放次数
playnum: "2w",
long: "2:47",
infonum: {
index: 2,
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
},
]
},
{
list:[]
},
{
list:[]
},
{
list:[]
}
]
}
},
onLoad() {
uni.getSystemInfo({
success:(res)=> {
let height = res.windowHeight-uni.upx2px(100);
this.swiperheight=height;
}
});
},
methods: {
// tabBar点击事件
tabtap(index) {
this.tabIndex=index;
},
// 滑动联动切换导航栏
tabChange(e) {
// console.log(JSON.stringify(e.detail));
this.tabIndex=e.detail.current;
}
}
}
script>
<style>
style>
swiper-tab-head文件
<template>
<view class="uni-tab-bar">
<scroll-view scroll-x class="uni-swiper-tab">
<block v-for="(tab, index) in tabBars" :key="tab.id">
<view class="swiper-tab-list" :class="{'active': tabIndex==index}" @tap="tabtap(index)">
{{tab.name}}
<view class="swiper-tab-line">view>
view>
block>
scroll-view>
view>
template>
<script>
export default {
props: {
tabBars: Array,
tabIndex: Number
},
methods: {
// tabBar点击事件
tabtap(index) {
this.$emit('tabtap', index);
},
}
}
script>
<style>
.swiper-tab-list {
color: #969696;
font-weight: bold;
}
.uni-swiper-tab {
border-bottom: 1upx solid #EEEEEE;
}
.uni-tab-bar .active {
color: #343434;
}
/* 被选中才出现下划线 */
.active .swiper-tab-line {
border-bottom: 6upx solid #FEDE33;
width: 70upx;
margin: 0 auto;
border-top: 6upx solid #FEDE33;
border-radius: 20upx;
}
style>
其余文件
其余文件和之前的项目文件大致相同
swiper
组件下的swiper-item
组件下的scroll-view
组件下增加一个view
组件,用于上拉加载swiper
组件下的swiper-item
组件下的scroll-view
组件的scrolltolower
事件绑定一个自定义函数loadmore(index)
loadmore
函数中,先根据newslist[index].loadtext
的内容判断加载状态,如果不是待加载状态,就不进行加载newslist[index].list
中,然后再把加载状态调整至待加载<template>
<view>
<view class="uni-tab-bar">
<swiper class="swiper-box" :style="{height: swiperheight+'px'}" :current="tabIndex" @change="tabChange">
<swiper-item v-for="(items, index) in newslist" :key="index">
<scroll-view scroll-y class="list" @scrolltolower="loadmore(index)">
<block v-for="(item, index1) in items.list" :key="index1">
<index-list :item="item" :index="index1">index-list>
block>
<view class="load-more">{{items.loadtext}}view>
scroll-view>
swiper-item>
swiper>
view>
view>
template>
<script>
import indexList from "../../components/index/index-list.vue";
import swiperTabHead from "../../components/index/swiper-tab-head.vue";
export default {
// 注册组件
components: {
indexList,
swiperTabHead
},
data() {
return {
//......
newslist: [
{
loadtext: "上拉加载更多",
list:[
{
//......
},
{
//......
},
]
},
{
loadtext: "上拉加载更多",
list:[
{
//......
},
{
//......
},
{
//......
},
{
//......
},
]
},
{
loadtext: "上拉加载更多",
list:[
{
//......
},
{
//......
},
]
},
{
loadtext: "上拉加载更多",
list:[]
},
{
loadtext: "上拉加载更多",
list:[]
},
{
loadtext: "上拉加载更多",
list:[]
}
]
}
},
onLoad() {
//......
},
methods: {
//......
// 上拉加载状态
loadmore(index) {
// 三种状态
// this.newslist[index].loadtext='上拉加载更多';
// this.newslist[index].loadtext='加载中';
// this.newslist[index].loadtext='没有更多数据了';
// 正在加载中或者没有更多数据的时候不会向服务器发送请求
if (this.newslist[index].loadtext!=='上拉加载更多') {
return;
}
// 模拟请求数据
this.newslist[index].loadtext='加载中'; // 修改状态
setTimeout(()=> {
// 获取完成
let obj = {
// 用户头像
userpic: "../../static/demo/userpic/12.jpg",
// 用户名
username: "昵称",
// 关注情况
isguanzhu: false,
// 标题
title: "这是标题",
// 类型(img-图文,video-视频)
type: "img",
// 封面图
titlepic: "../../static/demo/datapic/11.jpg",
// 赞、踩数,标记情况
infonum: {
index: 1, // 0-无操作,1-顶了,2-踩了
dingnum: 11,
cainum: 11,
},
commentnum: 10,
sharenum: 10
};
this.newslist[index].list.push(obj); // 加入数组
this.newslist[index].loadtext='上拉加载更多'; // 修改状态
}, 1000);
},
//......
}
script>
<style>
.load-more {
text-align: center;
color: #AAAAAA;
padding: 15upx 0;
}
style>
之前其实也描述过了封装的一般流程 点这里
这里再来描述一下:
{{items.loadtext}}
),剪切粘贴到刚才新建的页面中.load-more
的样式)剪切粘贴到刚才新建的页面中props
中注册,然后再把组件中使用的变量换成相应的变量名import 小驼峰名 from ".vue文件路径"
commponents
注册相应的组件:变量名="具体变量"
的形式传入index.vue文件
<template>
<view>
<view class="uni-tab-bar">
<load-more :loadtext="items.loadtext">load-more>
view>
view>
template>
<script>
//......
import loadMore from "../../components/common/load-more.vue";
export default {
// 注册组件
components: {
//......
loadMore
},
data() {
//......
},
onLoad() {
//......
},
methods: {
// 上拉加载状态
loadmore(index) {
//......
},
script>
<style>
style>
load-more.vue文件
<template>
<view class="load-more">{{loadtext}}view>
template>
<script>
export default {
props: {
loadtext: String
}
}
script>
<style scoped>
.load-more {
text-align: center;
color: #AAAAAA;
padding: 15upx 0;
}
style>
(index-list组件)
<template>
<view class="index-list animated rollIn">
<view class="index-list1 u-f-ac u-f-jsb">
<view class="u-f-ac">
<image :src="item.userpic" mode="widthFix" lazy-load>image>
{{item.username}}
view>
<view class="u-f-ac" v-show="!item.isguanzhu" @tap="guanzhu">
<view class="icon iconfont icon-zengjia">view>关注
view>
view>
<view class="index-list2" @tap="opendetail">{{item.title}}view>
<view class="index-list3 u-f-ajc" @tap="opendetail">
<image :src="item.titlepic" mode="widthFix" lazy-load>image>
<template v-if="item.type=='video'">
<view class="index-list-play icon iconfont icon-bofang">view>
<view class="index-list-playinfo">
{{item.playnum}}次播放 {{item.long}}
view>
template>
view>
<view class="index-list4 u-f-ac u-f-jsb">
<view class="u-f-ac">
<view class="u-f-ac" :class="{'active':(item.infonum.index==1)}" @tap="caozuo('ding')">
<view class="icon iconfont icon-icon_xiaolian-mian u-f-ac">view>
{{item.infonum.dingnum}}
view>
<view class="u-f-ac" :class="{'active':(item.infonum.index==2)}" @tap="caozuo('cai')">
<view class="icon iconfont icon-kulian u-f-ac">view>
{{item.infonum.cainum}}
view>
view>
<view class="u-f-ac">
<view class="u-f-ac">
<view class="icon iconfont icon-pinglun1 u-f-ac">view>
{{item.commentnum}}
view>
<view class="u-f-ac">
<view class="icon iconfont icon-zhuanfa u-f-ac">view>
{{item.sharenum}}
view>
view>
view>
view>
template>
<script>
export default {
props: {
item: Object,
index: Number
},
methods: {
// 关注
guanzhu() {
this.item.isguanzhu = true;
uni.showToast({
title: "关注成功"
});
},
// 顶踩
caozuo(type) {
switch(type) {
case 'ding':
if (this.item.infonum.index === 1) { // 已经顶了(取消顶)
--this.item.infonum.dingnum;
this.item.infonum.index = 0;
} else if (this.item.infonum.index === 2) { // 已经踩了(取消踩,顶)
++this.item.infonum.dingnum;
--this.item.infonum.cainum;
this.item.infonum.index = 1;
} else { // 未操作(顶)
++this.item.infonum.dingnum;
this.item.infonum.index = 1;
}
break;
case 'cai':
if (this.item.infonum.index === 2) { // 已经踩了(取消踩)
--this.item.infonum.cainum;
this.item.infonum.index = 0;
} else if (this.item.infonum.index === 1) { // 已经顶了(取消顶,踩)
++this.item.infonum.cainum;
--this.item.infonum.dingnum;
this.item.infonum.index = 2;
} else { // 未操作(踩)
++this.item.infonum.cainum;
this.item.infonum.index = 2;
}
break;
}
},
// 进入详情页
opendetail() {
// 暂时留一个接口,先不做具体实现
console.log("进入详情页");
}
},
}
script>
<style scoped>
.index-list {
padding: 20upx;
border-bottom: 1upx solid #EEEEEE;
}
.index-list1>view:first-child {
color: #999999;
}
.index-list1>view:first-child image {
width: 90upx;
height: 90upx;
border-radius: 50%;
margin-right: 10upx;
}
.index-list1>view:last-child {
background-color: #F4F4F4;
border-radius: 5upx;
padding: 0 10upx;
}
.index-list2 {
padding-top: 15upx;
font-size: 32upx;
}
.index-list3 {
position: relative;
padding-top: 15upx;
}
.index-list3>image {
width: 100%;
border-radius: 20upx;
}
.index-list4 {
padding: 15upx 0;
}
.index-list4>view {
color: #999999;
}
.index-list4>view>view:first-child, .index-list4>view>view>view {
margin-right: 10upx;
}
.index-list-play {
position: absolute;
font-size: 140upx;
color: #FFFFFF;
}
.index-list-playinfo {
position: absolute;
background-color: rgba(51, 51, 51, 0.62);
color: #FFFFFF;
bottom: 8upx;
right: 8upx;
border-radius: 40upx;
font-size: 22upx;
padding: 0 12upx;
}
.index-list4 .active, .index-list4 .active > view{
color: #C5F61C;
}
style>
对于无数据的页面,优化了展示的效果,不再是几乎一片空白,并将组件进行了封装。
index.vue文件
<template>
<view>
<swiper-tab-head :tabBars="tabBars" :tabIndex="tabIndex" @tabtap="tabtap">swiper-tab-head>
<view class="uni-tab-bar">
<template v-if="items.list.length > 0">
<block v-for="(item, index1) in items.list" :key="index1">
<index-list :item="item" :index="index1">index-list>
block>
<load-more :loadtext="items.loadtext">load-more>
template>
<template v-else>
<nothing>nothing>
template>
view>
view>
template>
<script>
//......
import nothing from "../../components/common/nothing.vue";
export default {
// 注册组件
components: {
//......
nothing
},
data() {
return {
//......
}
},
onLoad() {
//......
},
methods: {
//......
}
}
script>
<style>
style>
nothing.vue文件
<template>
<view class="nothing u-f-ajc animated fadeIn">
<image src="../../static/common/nothing.png" mode="widthFix">image>
view>
template>
<script>
script>
<style>
.nothing {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.nothing > image {
width: 50%;
}
style>