uniapp项目实战 新闻类app
项目实战 慕课新闻
阿里云服务空间名:moocnews200909
第5章 扬帆起航,胜利在向你招手 (首页功能模块)
5-1 项目初始化.mp4
初始化云数据库
使用方式
- 在
cloudfucntions
目录右键即可创建db_init.json
, - 编写好json内容,在
db_init.json
上右键初始化数据库。
参考样式
{
"collection_test": { // 集合(表名)
"data": [ // 数据
{
"_id": "da51bd8c5e37ac14099ea43a2505a1a5",
"name": "tom"
}
],
"index": [{ // 索引
"IndexName": "index_a", // 索引名称
"MgoKeySchema": { // 索引规则
"MgoIndexKeys": [{
"Name": "index", // 索引字段
"Direction": "1" // 索引方向,1:ASC-升序,-1:DESC-降序,2dsphere:地理位置
}],
"MgoIsUnique": false // 索引是否唯一
}
}]
}
}
制作tabbar
创建3个页面文件
配置pages.json(底部的页签内容编辑器会自动提示作用)
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/tabbar/index/index",
"style": {
"navigationStyle": "custom",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "uni-app"
}
}, {
"path": "pages/tabbar/follow/follow",
"style": {}
}, {
"path": "pages/tabbar/my/my",
"style": {}
}, {
"path": "pages/home-search/home-search",
"style": {
"navigationStyle": "custom"
}
}, {
"path": "pages/home-label/home-label",
"style": {
"navigationBarTitleText": "标签管理"
}
}, {
"path": "pages/home-detail/home-detail",
"style": {}
}
,{
"path" : "pages/detail-comments/detail-comments",
"style" : {}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#666",
"selectedColor": "#f07373",
"backgroundColor": "#fff",
"list": [{
"pagePath": "pages/tabbar/index/index",
"iconPath": "static/home.png",
"selectedIconPath": "static/home-active.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": "我的"
}]
}
}
5-2 自定义导航栏.mp4
首页结果:
3部分:
- 导航栏
- 选项卡
- 卡片列表
制作搜索框
1. 去除默认导航栏进行自定义
"style": {
"navigationStyle": "custom",/* 自定义导航栏 */
"navigationBarTextStyle": "white",/* 导航栏前景色 */
"navigationBarTitleText": "uni-app"/* 导航栏 文字信息 */
}
2.通过自定义组件, 实现自定义导航栏
创建组件navbar.vue
页面内引用注册使用
页面访问方式:http://localhost:8080/#/pages/tabbar/index/index
easyCom引入组件的方法
当目录和组件名一致的时候,不需要引入,可以直接使用组件(不需要import引入)
使用这个方法为局部引入
页面内输入:
<!-- 自定义导航栏 -->
<navbar></navbar>
导航栏实现
添加样式的公共变量
/* 颜色变量 */
@mk-base-color : #f07373;
navbar.vue
<template>
<view class="navbar">
<view class="navbar-fixed">
<view class="navbar-content">
<view class="navbar-search">
<!-- 非搜索页显示 -->
<view class="navbar-search_icon">
<uni-icons type="search" size="16" color="#999"></uni-icons>
</view>
<view class="navbar-serach">
<!-- 搜索页显示 -->
<input class="navbar-search_text" type="text" v-model="val" placeholder="请输入您要搜索的内容" />
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'navbar',
data() {
return {
val: ''
};
}
}
</script>
<style lang="less">
@import '../../common/css/icons.css';
@import './../../uni.less';
.navbar {
.navbar-fixed {
position: fixed;
top: 0;
left: 0;
z-index: 99;
width: 100%;
background-color: @mk-base-color;
.navbar-content {
display: flex;
justify-content: center;
align-items: center;
padding: 0 15px;
height: 45px;
box-sizing: border-box;
.navbar-search {
display: flex;
align-items: center;
padding: 0 10px;
width: 100%;
height: 30px;
border-radius: 30px;
background-color: #fff;
.navbar-search_icon {
// width: 10px;
// height: 10px;
margin-right: 10px;
}
.navbar-search_text {
width: 100%;
font-size: 14px;
color: #999;
}
}
&.search {
padding-left: 0;
.navbar-content__search-icons {
margin-left: 10px;
margin-right: 10px;
}
.navbar-search {
border-radius: 5px;
}
}
}
}
}
</style>
uni.scss 作为公共的样式色值在组件内可以直接使用(我改成less进行使用)
运行结果
5-3 导航栏适配小程序
H5中无状态栏,小程序中有状态栏
微信小程序测试运行时需要将端口打开
错误提示,操作方式
1.报错处理
1. 工具的服务端口已关闭。
要使用命令行调用工具,请在下方输入 y 以确认开启,或手动打开工具 -> 设置 -> 安全设置,将服务端口开启。
2. 解决微信开发者工具打开微信小程序项目页面显示不出来
1.运用命令行安装依赖 npm i
2.进入微信开发者工具点击工具选择构建npm,刷新页面就行.
2.显示问题:小程序中顶部状态栏遮挡,右侧胶囊将搜索框遮挡住
处理方式:
1.设定状态栏高度,导航栏高度,窗口宽度
data() {
return {
statusBarHeight: 20,/* 状态栏高度 */
navBarHeight: 45,/* 导航栏高度 */
windowWidth: 375,/* 窗口宽度 */
/* 设定状态栏默认高度 */
val: ''/* 导航栏搜索框的值 */
};
},
2.分别根据设备调用api,计算对应的高度和位置
使用uniapp条件编译
#ifdef : if defined 仅在某个平台编译
#ifndef : if not defined 在除里该平台的其他编译
#endif : end if 结束条件编译
%PLATFORM% 需要编译的平台,上面的MP就是各个小程序的意思
created() {
// 获取手机系统信息
const info = uni.getSystemInfoSync()
// 设置状态栏高度(H5顶部无状态栏小程序有状态栏需要撑起高度)
this.statusBarHeight = info.statusBarHeight
this.windowWidth = info.windowWidth
// 除了h5 app mp-alipay的情况下执行
// #ifndef H5 || APP-PLUS || MP-ALIPAY
// 获取胶囊的位置
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
console.log(menuButtonInfo);
// (胶囊底部高度 - 状态栏的高度) + (胶囊顶部高度 - 状态栏内的高度) = 导航栏的高度
this.navBarHeight = (menuButtonInfo.bottom - info.statusBarHeight) + (menuButtonInfo.top - info.statusBarHeight)
this.windowWidth = menuButtonInfo.left
// #endif
}
3.赋值给对应的dom元素
<template>
<view class="navbar">
<view class="navbar-fixed">
<!-- 状态栏小程序撑起高度 -->
<view :style="{height:statusBarHeight+'px'}"></view>
<view class="navbar-content" :style="{height:navBarHeight+'px',width:windowWidth+'px'}">
<view class="navbar-search">
<view class="navbar-search_icon">
<uni-icons type="search" size="16" color="#999"></uni-icons>
</view>
<view class="navbar-serach">
<input class="navbar-search_text" type="text" v-model="val" placeholder="请输入您要搜索的内容" />
</view>
</view>
</view>
</view>
</view>
</template>
修改状态栏颜色从黑色改成白色:前景色 根目录:pages.json
"navigationBarTextStyle": "white",/* 导航栏前景色 */
运行结果
5-4 使用字体图标.mp4
1.使用iconfont阿里图标库
1.iconfont中获取字体图标路径
2.将对应的css文件下载到common/css/icons.css文件内
3.引入到css中
@import '../../common/css/icons.css';
4.页面内使用
<text class="iconfont icon-search"></text>
2.插件市场使用icon
1.插件的安装
网址:1. 地址
在components文件夹下显示uni-icons文件夹
2.插件的使用
<view class="navbar-search_icon">
<uni-icons type="search" size="16" color="#999"></uni-icons>
</view>
网址:https://ext.dcloud.net.cn/plugin?id=28
使用方式
在 script
中引用组件(现有项目无需格外引入只需要安装插件即可)
import uniIcons from "@/components/uni-icons/uni-icons.vue"
export default {
components: {uniIcons}
}
在 template
中使用组件
<uni-icons type="contact" size="30"></uni-icons>
属性说明
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
size | Number | 24 | 图标大小 |
type | String | - | 图标图案,参考示例 |
color | String | - | 图标颜色 |
事件说明
事件名 | 说明 | 返回值 |
---|---|---|
@click | 点击 Icon 触发事件 | - |
5-5 选项卡展示
1.创建tab组件
2.页面横向滚动组件书写
<scroll-view class="tab-scroll" scroll-x="true" >
<view class="tab-scroll__box">
<view v-for="item in 10" class="tab-scroll__item">
{{item}}内容
</view>
</view>
</scroll-view>
3.给顶部导航栏组件添加占位符撑起高度
注意:需要添加占位符高度 状态栏高度+导航栏高度(否则下面tab会塌陷)
navbar.vue文件
<!-- 需要添加占位符高度 状态栏高度+导航栏高度(否则下面tab会塌陷)-->
<view :style="{height: statusBarHeight+navBarHeight+'px'}"></view>
样式中同行显示设定
.tab-scroll
最大宽度
.tab-scroll {
max-width: calc(100vw - 45px);
}
tab.vue完整代码
<template>
<view class="tab">
<scroll-view class="tab-scroll" scroll-x>
<view class="tab-scroll__box">
<view v-for="item in 10" class="tab-scroll__item">
{{item}}内容
</view>
</view>
</scroll-view>
<view class="tab-icons" @click="open">
<uni-icons type="gear" size="26" color="#666"></uni-icons>
</view>
</view>
</template>
<script>
export default {
data() {
return {
};
}
}
</script>
<style lang="less">
@import '../../common/css/icons.css';
@import './../../uni.less';
.tab {
display: flex;
width: 100%;
border-bottom: 1px #f5f5f5 solid;
background-color: #fff;
box-sizing: border-box;
.tab-scroll {
flex: 1;
overflow: hidden;
box-sizing: border-box;
max-width: calc(100vw - 45px);
.tab-scroll__box {
display: flex;
align-items: center;
flex-wrap: nowrap;
height: 45px;
box-sizing: border-box;
.tab-scroll__item {
flex-shrink: 0;
padding: 0 10px;
color: #333;
font-size: 14px;
&.active {
color: @mk-base-color;
}
}
}
.tab-scroll__box {
display: flex;
align-items: center;
flex-wrap: nowrap;
height: 45px;
box-sizing: border-box;
.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 {
content: '';
position: absolute;
top: 12px;
bottom: 12px;
left: 0;
width: 1px;
background-color: #ddd;
}
}
}
</style>
5-6 选项卡数据初始化
所有的数据表文件 项目根目录的文件夹内
1.创建云函数get_label
2.创建对应云数据库数据表
导入文件夹内的文件即可
3.云函数 get_label书写返回分类数据
'use strict';
// 获取数据库的引用
const db = uniCloud.database()
exports.main = async (event, context) => {
// 获取 label 表的数据
let label = await db.collection('label').get()
//返回数据给客户端
return {
code: 200,
msg: '数据请求成功',
data: label.data
}
};
4.页面内方法调用接口
网址:客户端调用云函数
getLabel() {
// 客户端调用云函数方法
const data = uniCloud.callFunction({
name:"get_label",
success(res){
console.log(res)
},
fail(err){console.log(err)},
complete(){}
})
}
5.组件传值
index.vue内
<tab :list="tabList"></tab>
export default {
data() {
return {
tablist:[]
}
},
methods: {
},
onLoad() {
console.log('dasasdasd')
this.getLabel();
console.log(this.tablist)
},
methods: {
getLabel() {
const _this = this;
// 客户端调用云函数方法
const data = uniCloud.callFunction({
name:"get_label",
success(res){
_this.tablist = res.result.data;
},
fail(err){console.log(err)},
complete(){}
})
}
}
}
补充:客户端调用云函数方法写法2
getLabel() {
const _this = this;
// 客户端调用云函数方法
const data = uniCloud.callFunction({
name:"get_label",
}).then((res)=>{
console.log(res)
_this.tablist = res.result.data;
}).catch((err)=>{
console.log(err)
})
}
组件内写法
<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>
props: {
list: {
type: Array
}
},
5-7 封装数据请求
目的: 只关注成功的返回,失败的返回统一处理
在common - api - index.js
封装成的结果
this.$api.get_label().then((res) => {
})
步骤
|-common
| |-api api接口调用
| |-css 公共样式
|-http.js 封装网络请求
1.promise方法封装uniCloud.callFunction云函数请求方法
common目录下的http.js
const get_label = (data)=> {
return new Promise((reslove, reject) => {
uniCloud.callFunction({
name: url,
data: dataObj
}).then((res) => {
if (res.result.code === 200) {
// .then
reslove(res.result)
} else {
// catch
reject(res.result)
}
}).catch((err) => {
reject(err)
})
})
}
export default {
get_label
}
在api目录下创建list.js(测试方法可行性)
/* 所有的云函数接口列表写在这里 */
export const get_list = (data)=>{
return new Promise((reslove, reject) => {
reslove({'data':'请求成功'})
})
}
绑定到根目录的main.js上
import api from './common/api'
Vue.prototype.$api = api /*绑定到vue实例上*/
2.测试方法重写
index.vue内调用 重写
测试方法get_list
this.$api.get_list().then((res)=>{
console.log(res)
})
this.$api.get_label({
name:"get_label"
}).then((res) => {
const {
data
} = res
console.log('标签 ',data);
this.tabList = data
// console.log(this.tabList);
})
3.创建http.js 作为方法调用(创建一个http的接口方法)
目的:为了将return 内的 Promise 提出作为公共部分
export default function $http(options){
const {url,data} = options;
return new Promise((reslove, reject) => {
uniCloud.callFunction({
name: url,/* 云函数名称 */
data: data/* 传递的数据 */
}).then((res) => {
if (res.result.code === 200) {
// .then
reslove(res.result)
} else {
// catch
reject(res.result)
}
}).catch((err) => {
reject(err)
})
})
}
4.修改原先的list.js
方法: 引入http.js 将原先的返回值方法进行改造
import $http from './../http.js'
export const get_label = (data)=> {
return $http({
url:'get_label',
data
})
}
/* 所有的云函数接口列表写在这里 */
export const get_list = (data)=>{
return new Promise((reslove, reject) => {
reslove({'data':'请求成功'})
})
}
5.统一封装云函数请求,在list内统一调用
之后如果有多个云函数只需要重复写多个方法函数即可
import $http from './../http.js'
export const get_label = (data)=> {
return $http({
url:'get_label',
data
})
}
6.由于有多个云函数,需要批量的导出和引用,需要改写index.js文件
原先只能导出一个云函数(现在需要无论名称是什么都可以全部导入导出)
//原先的===============
// import {get_label,get_list} from './list.js'
// export default{
// get_label,
// get_list
// }
//修改后的================
// 批量导出文件
const requireApi = require.context(
// api 目录的相对路径
'.',
// 是否查询子目录
false,
// 查询文件的一个后缀
/.js$/
)
let module = {}
requireApi.keys().forEach((key,index)=>{
//因为index为输出的目录,所以需要排除掉
if(key === './index.js') return
console.log(key);
//对象合并
Object.assign(module,requireApi(key))
})
console.log(module)
export default module
得到需要导出的格式
最终结果
7.在api内创建list.js进行调用
http.js
export default function $http(options) {
const {
url,
data
} = options
const dataObj = {
user_id: '5e76254858d922004d6c9cdc',
...data
}
return new Promise((reslove, reject) => {
uniCloud.callFunction({
name: url,
data: dataObj
}).then((res) => {
if (res.result.code === 200) {
// .then
reslove(res.result)
} else {
// catch
reject(res.result)
}
}).catch((err) => {
reject(err)
})
})
}
list.js
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
})
}
export const update_like = (data) => {
return $http({
url: 'update_like',
data
})
}
export const get_search = (data) => {
return $http({
url: 'get_search',
data
})
}
export const update_label = (data) => {
return $http({
url: 'update_label',
data
})
}
export const get_detail = (data) => {
return $http({
url: "get_detail",
data
})
}
export const update_comment = (data) => {
return $http({
url: "update_comment",
data
})
}
export const get_comments = (data) => {
return $http({
url: 'get_comments',
data
})
}
export const update_author = (data) =>{
return $http({
url: 'update_author',
data
})
}
export const update_thumbsup = (data) =>{
return $http({
url: 'update_thumbsup',
data
})
}
index.js
// 批量导出文件
const requireApi = require.context(
// api 目录的相对路径
'.',
// 是否查询子目录
false,
// 查询文件的一个后缀
/.js$/
)
let module = {}
requireApi.keys().forEach((key,index)=>{
if(key === './index.js') return
console.log(key);
Object.assign(module,requireApi(key))
})
export default module
方法调用
this.$api.get_label({
name:"get_label"
}).then((res) => {
const {
data
} = res
console.log('标签 ',data);
data.unshift({
name:'全部'
})
this.tabList = data
// console.log(this.tabList);
})
5-8 选项卡切换高亮
步骤:
1.绑定点击事件注册
tab.vue
<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"
@click="clickTab(item, index)">{{item.name}}</view>
</view>
</scroll-view>
事件绑定:
clickTab(item, index) {
console.log(item,index);
this.activeIndex = index
this.$emit('tab', {
data: item,
index: index
})
},
2.获得点击对象的name值和索引值
通过动态绑定类,进行申明
<view class="tab-scroll__box">
<view v-for="(item, index) in list" :key="index" class="tab-scroll__item" :class="{active:activeIndex === index}"
@click="clickTab(item, index)">{{item.name}}</view>
</view>
.tab-scroll__item {
flex-shrink: 0;
padding: 0 10px;
color: #333;
font-size: 14px;
&.active {
color: $mk-base-color;
}
}
3.将事件传递给父级页面
clickTab(item, index) {
console.log(item,index);
this.activeIndex = index
this.$emit('tab', {
data: item,/* 点击的内容 */
index: index/* 点击的索引 */
})
},
4.在首页的tab组件内接受事件
@tab="tab"
<tab :list="tabList" :tabIndex="tabIndex" @tab="tab"></tab>
注册事件:
tab({data,index}){
console.log(data,index);
this.activeIndex = index
},
list-item组件
<template>
<list-scroll class="list-scroll" @loadmore="loadmore">
<list-card mode="base" :item="item" v-for="item in list" :key="item._id"></list-card>
<uni-load-more v-if="list.length === 0 || list.length > 7" iconType="snow" :status="load.loading"></uni-load-more>
</list-scroll>
</template>
<script>
export default {
props: {
list: {
type: Array,
default () {
return []
}
},
load: {
type: Object,
default () {
return {
loading: "loading"
}
}
}
},
methods: {
loadmore() {
this.$emit('loadmore')
}
}
}
</script>
<style>
.list-scroll {
height: 100%;
}
</style>
5-9 基础卡片视图实现
1.将顶部tab栏固定在顶部不随着页面滑动
处理方式: 将滚动的内容区域放置在scroll-view
标签内
<view class="home">
<navbar></navbar>
<tab :list="tabList" @tab="tab"></tab>
<view class="scroll">
<scroll-view scroll-y="true" class="list-scroll" >
<view>
<view v-for="item in 100">
{{item}}的内容
</view>
</view>
</scroll-view>
</view>
</view>
通过flex竖向布局实现剩余内容高度撑满屏幕
page {
height: 100%;
display: flex;
}
.home {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
.scroll{
flex: 1;
overflow: hidden;
box-sizing: border-box;
.list-scroll{
height: 100%;
display: flex;
flex-direction: column;
}
}
.home-list {
flex:1;
box-sizing: border-box;
}
}
2.将内容放置在list-scroll组件内
创建list-scroll组件
作用:作为滚动区域的组件
<template>
<view class="scroll">
<scroll-view scroll-y="true" class="list-scroll" >
<view>
<slot></slot>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style lang="less">
.scroll {
flex: 1;
overflow: hidden;
box-sizing: border-box;
.list-scroll {
height: 100%;
display: flex;
flex-direction: column;
}
}
</style>
3.组件list-card循环列表创建
<template>
<view class="home">
<navbar></navbar>
<tab :list="tabList" @tab="tab"></tab>
<list-scroll>
<list-card v-for="(item,i) in 10"></list-card>
</list-scroll>
</view>
</template>
4.构建卡片组件list-card基础卡片
基础组件构建
<template>
<view>
<!-- 基础卡片-->
<view class="listcard">
<view class="listcard-image">
<!-- aspectFill代表完全填充图片内容-->
<image src="../../static/logo.png" mode="aspectFill"></image>
</view>
<view class="listcard-content">
<view class="listcard-content__title">
<text>文章标题</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">浏览量浏览</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
};
}
}
</script>
5-10 更多卡片视图实现
1.list-card添加多图模式和大图模式
<template>
<view>
<!-- 基础卡片-->
<view class="listcard">
<view class="listcard-image">
<!-- aspectFill代表完全填充图片内容-->
<image src="../../static/logo.png" mode="aspectFill"></image>
</view>
<view class="listcard-content">
<view class="listcard-content__title">
<text>文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题文章标题</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">浏览量浏览</view>
</view>
</view>
</view>
<!-- 多图模式 -->
<view class="listcard mode-column">
<view class="listcard-content">
<view class="listcard-content__title">
<text>文章标题</text>
</view>
<view class="listcard-image">
<view v-for="item in 3" :key="index" 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">浏览</view>
</view>
</view>
</view>
<!-- 大图模式 -->
<view 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>文章标题</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">浏览</view>
</view>
</view>
</view>
</view>
</template>
2.list-card添加props渲染不同视图
添加父传子props接受的参数,
基础卡片:v-if="item.mode === 'base'"
多图模式:v-if="item.mode === 'column'"
大图模式:v-if="item.mode === 'image'"
默认为mode=base
<template>
<view>
<!-- 基础卡片-->
<view v-if="mode==='base'" class="listcard">
<view class="listcard-image">
<!-- aspectFill代表完全填充图片内容-->
<image src="../../static/logo.png" mode="aspectFill"></image>
</view>
<view class="listcard-content">
<view class="listcard-content__title">
<text>文章标题文章</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">浏览量浏览</view>
</view>
</view>
</view>
<!-- 多图模式 -->
<view v-if="mode==='column'" class="listcard mode-column">
<view class="listcard-content">
<view class="listcard-content__title">
<text>文章标题</text>
</view>
<view class="listcard-image">
<view v-for="item in 3" :key="index" 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">浏览</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>文章标题</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">浏览</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
mode: {
type: String,
default: 'base'
}
},
data() {
return {};
}
}
</script>
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>
5-11 实现内容切换
1.list组件制作
作用:用来放置swiper左右滑动列表
list.vue
<template>
<swiper class="home-swiper">
<swiper-item class="swiper-item">
<list-scroll class='list-scroll'>
<list-card mode="base"></list-card>
<list-card mode="column"></list-card>
<list-card mode="image"></list-card>
</list-scroll>
</swiper-item>
<swiper-item class="swiper-item">
<list-scroll class='list-scroll'>
<list-card mode="base"></list-card>
<list-card mode="column"></list-card>
<list-card mode="image"></list-card>
</list-scroll>
</swiper-item>
</swiper>
</template>
index.vue将原先内容注释
<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 class="home-list">
<list></list>
</view>
</view>
可左右滑动
2.制作组件list-item
在list组件内新建list-item组件,将内容区域list-scroll
提出来进行嵌套
list-item.vue
<template>
<list-scroll class='list-scroll'>
<list-card mode="base"></list-card>
<list-card mode="column"></list-card>
<list-card mode="image"></list-card>
</list-scroll>
</template>
<script>
</script>
<style lang="less">
.list-scroll{
height:100%
}
</style>
由于list-item不符合对应规范需要单独引入
list.vue
<template>
<swiper class="home-swiper">
<swiper-item class="swiper-item">
<list-item></list-item>
</swiper-item>
<swiper-item class="swiper-item">
<list-item></list-item>
</swiper-item>
<swiper-item class="swiper-item">
<list-item></list-item>
</swiper-item>
</swiper>
</template>
<script>
/* 因为不符合规范需要手动注册引入*/
import listitem from './list-item.vue'
export default {
components:{'list-item':listitem}
}
</script>
结果:
3.在list传入tab实现高亮
先从index.vue内传入tablist然后循环列表tab
index.vue
<view class="home-list">
<list :tab = "tabList"></list>
</view>
list.vue
<template>
<swiper class="home-swiper">
<swiper-item v-for="(item,index) in tab" :key="index" class="swiper-item">
<list-item></list-item>
</swiper-item>
</swiper>
</template>
<script>
/* 因为不符合规范需要手动注册引入*/
import listitem from './list-item.vue'
export default {
components:{'list-item':listitem},
props:{
tab:Array,
default(){
return [];
}
}
}
</script>
结果:实现列表的个数与选项卡一一对应
5-12 选项卡与内容联动
1.swiper监听左右滚动 change事件监听滑动
<template>
<swiper class="home-swiper" @change="change">
<swiper-item v-for="(item,index) in tab" :key="index" class="swiper-item">
<list-item></list-item>
</swiper-item>
</swiper>
</template>
<script>
/* 因为不符合规范需要手动注册引入*/
import listitem from './list-item.vue'
export default {
components:{'list-item':listitem},
props:{
tab:Array,
default(){
return [];
}
},
methods:{
change(e){
console.log(e)
}
}
}
</script>
方法会返回的current值为当前第几项
activeIndex可以控制当前返回第几项
方法:将current值传给activeIndex 实现高亮显示切换联动
list.vue
change(e){
/* 获得curent属性*/
const {current} = e.detail
console.log(e)
/* 将current值传给tab顶部组件实现高亮切换联动*/
this.$emit('change',current)
}
index.vue
2.添加change事件进行监听变化,传值给tab组件实现高亮切换
<list :tab = "tabList" @change="change"></list>
/* 接收子组件的事件*/
change(current){
/* 当前current的值*/
console.log(current)
/* 再将值传给table顶部的tab组件*/
this.tabIndex =current;
},
tab组件引用处接受传递的参数
<tab :list="tabList" @tab="tab" :tabIndex="tabIndex"></tab>
3.通过watch监听props中传值的变化实现切换效果
tab.vue组件
原先tab组件通过activeIndex 控制高亮显示
通过props接受tabIndex的值
props: {
list: {
type: Array
},
/* 切换的高亮显示项*/
tabIndex:{
type:Number,
default:0
}
},
watch监听props变化
watch:{
tabIndex(newVal,oldVal){
this.activeIndex = newVal
}
},
4.当点击tab的时候底部的列表也切换
给tab点击后this.$emit
传值给index,再从index传值给list
tab之前就写了tab$emit方法tab
tab.vue
clickTab(item,index) {
this.activeIndex = index;
this.$emit('tab',{
data:item,/* 传递的数据 */
index:index/* 传递的索引值 */
})
}
index.vue
在index.vue中先申明变量data activeIndex:0
tab内接收的参数传给activeIndex
/* 接收组件传递值*/
tab({data,index}){
console.log(data)
console.log(index)
this.activeIndex =index;
}
props组件传值activeIndex
<list :tab = "tabList" :activeIndex="activeIndex" @change="change"></list>
swiper的current属性为跳转到第几项
list.vue
proprs接收参数
/* 切换的高亮显示项*/
activeIndex:{
type:Number,
default:0
}
参数赋值给swiper组件:current="activeIndex"
<swiper class="home-swiper" :current="activeIndex" @change="change">
<swiper-item v-for="(item,index) in tab" :key="index" class="swiper-item">
<list-item></list-item>
</swiper-item>
</swiper>
5-13 内容卡片数据初始化
创建云函数git_list获取列表信息
git_list
'use strict';
// 获取数据库的引用
const db = uniCloud.database()
exports.main = async (event, context) => {
const list = await db.collection('aritcle').get()
// 返回数据给客户端
return list
};
将返回的结果过滤掉其中的content,使用方法(field)
最后优化结果
'use strict';
// 获取数据库的引用
const db = uniCloud.database()
exports.main = async (event, context) => {
const list = await db.collection('article')
.field({
/* true表示只返回这个字段,false表示不返回*/
content:false
})
.get()
// 返回数据给客户端
return{
code:200,
msg:"数据请求成功",
data:list.data
}
};
在接口的common/api/list 增加返回
/* 所有的云函数接口列表写在这里 */
export const get_list = (data)=>{
return $http({
url:'get_list',
data
})
}
在components/list/list.vue调用对应云函数获取数据
/* 获得对应的信息,需要将分类参数传递给云函数*/
getList(name){
this.$api.getList().then((res)=>{
console.log(res);
const {data} = res;
this.list = data;
})
}
调用list
组件内不能用onload只能用created
created(){
/* 初始化调用*/
this.getList()
},
完整的list.vue
<template>
<swiper class="home-swiper" :current="activeIndex" @change="change">
<swiper-item v-for="(item,index) in tab" :key="index" class="swiper-item">
<list-item :list="list"></list-item>
</swiper-item>
</swiper>
</template>
<script>
/* 因为不符合规范需要手动注册引入*/
import listitem from './list-item.vue'
export default {
components:{'list-item':listitem},
data:function(){
return {
list:[]
}
},
props:{
tab:Array,
default(){
return [];
},
/* 切换的高亮显示项*/
activeIndex:{
type:Number,
default:0
}
},
/* onload在页面上,created组件*/
created(){
/* 初始化调用*/
this.getList()
},
methods:{
change(e){
/* 获得curent属性*/
const {current} = e.detail
/* 获得对应的分类名称*/
// console.log(this.tab[current].name)
/* 每次变更将获取到的值传递给getList实现数据变更*/
// this.getList(this.tab[current].name)
/* 将current值传给tab顶部组件实现高亮切换联动*/
this.$emit('change',current)
},
/* 获得对应的信息,需要将分类参数传递给云函数*/
getList(){
this.$api.get_list().then((res)=>{
console.log(res);
const {data} = res;
this.list = data;
})
}
}
}
</script>
<style lang="less">
.home-swiper{
height: 100%;
.swiper-item{
height:100%;
overflow:hidden;
}
}
</style>
在list下的list-item.vue内,
先修改list-item.vue 实现内容循环遍历,并传值:item=item
给list-card
<template>
<list-scroll class='list-scroll'>
<list-card mode="base" :item=item v-for="item in list" :key="item._id">
</list-card>
</list-scroll>
</template>
<script>
export default{
props:{
list:{
type:Array,
default(){
return []
}
}
}
}
</script>
<style lang="less">
.list-scroll{
height:100%
}
</style>
修改对应的list-card内容显示不同的内容信息接受item的内容
在props内接受对象item,默认返回空对象
props: {
item:{
type:Object,
default(){
return {}
}
}
},
原先的mode可以清楚,在item内包含mode
修改上方模板的判断为item.mode==='模板类型'
修改填充对应的字段内容
注:多图模式中需要增加判断是否小于3才渲染
v-if="index < 3"
list-card.vue完整代码
<template>
<view>
<!-- 基础卡片-->
<view v-if="item.mode==='base'" class="listcard">
<view class="listcard-image">
<!-- aspectFill代表完全填充图片内容-->
<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: {
type: String,
default: 'base'
},
item:{
type:Object,
default(){
return {}
}
}
},
data() {
return {};
}
}
</script>
<style lang="less">
@import './../../uni.less';
.listcard {
display: flex;
padding: 10px;
margin: 10px;
border-radius: 5px;
box-shadow: 0 0 5px 1px rgba(0, 0, 0, 0.1);
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 {
position: relative;
padding-right: 30px;
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: 0;
}
.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: 0;
}
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>
5-14 切换选项卡懒加载数据
使用接口gitlist获取真实数据并渲染页面
1.确定不同内容显示在页面上的不同标签(做分类)
在components/list/list.vue内获取到对应分分类
在change事件内获取到对应的下标值,根据下标值获取对应的分类
change(e){
/* 获得curent属性*/
const {current} = e.detail
console.log(this.tab[current])
/* 将current值传给tab顶部组件实现高亮切换联动*/
this.$emit('change',current)
}
使用this.tab[current].name
可以获得到对应的name值,
2.将对应的名称name值传递给云函数获得对应的分类信息
list内创建方法getList并传递参数name
/* 获得对应的信息,需要将分类参数传递给云函数*/
getList(name){
this.$api.getList({name}).then((res)=>{
console.log(res);
const {data} = res;
this.list = data;
})
}
在页面初始化加载的时候调用created中调用,和在onchange事件切换的时候调用
created(){
/* 初始化调用*/
this.getList('后端开发')
},
change(e){
/* 获得curent属性*/
const {current} = e.detail
/* 获得对应的分类名称*/
console.log(this.tab[current].name)
/* 每次变更将获取到的值传递给getList实现数据变更*/
this.getList(this.tab[current].name)
/* 将current值传给tab顶部组件实现高亮切换联动*/
this.$emit('change',current)
},
云函数获取event获取数据并进行筛选聚合操作
使用聚合操作进行筛选数据.
云函数 get_list/index.js
'use strict';
// 获取数据库的引用
const db = uniCloud.database()
exports.main = async (event, context) => {
/* 接收分类去筛选数据*/
const {name} = event;
let matchObj = {}
if (name !== '全部') {
matchObj = {
classify: name/* 筛选字段将符合条件的文档传递给下一个流水线 */
}
}
// 聚合 : 更精细化的去处理数据 求和 、分组、指定那些字段
const list = await db.collection('article')
.aggregate()/* 获得聚合操作的集合 */
.match(matchObj)/* 筛选字段将符合条件的文档传递给下一个流水线 */
.project({
/* false表示不返回*/
content:false
})
.end()/* 发起实际聚合操作 */
// 返回数据给客户端
return{
code:200,
msg:"数据请求成功",
data:list.data
}
};
由于渲染新的内容会闪烁显示,增加缓存功能
在getList内添加缓存的下标current
添加data值listCatchData = {};
data:function(){
return {
list:[],
/* 处理切换的时候内容闪烁的状态(设定缓存状态)*/
listCatchData = {};
}
},
/* 获得对应的信息,需要将分类参数传递给云函数*/
getList(current){
this.$api.get_list({
name:this.tab[current].name
}).then((res)=>{
console.log(res);
const {data} = res;
this.list = data;
/* 赋值current进行缓存*/
this.listCatchData[current] = data
})
}
初始化的调用填入为
需要注意当内容没有渲染的时候getList在created内执行会报错
/* 初始化调用*/
this.getList(this.activeIndex);
修改为将created中的方法在watch中进行调用
/* 当tab发生变化的时候才进行渲染*/
watch:{
tab(newVal) {
if (newVal.length === 0) return;
this.listCatchData = {};
this.getList(this.activeIndex);
},
},
/* onload在页面上,created组件*/
created(){
/* 初始化调用*/
/* tab是没有赋值*/
},
修改模板渲染的部分
<list-item :list="listCatchData[index]"></list-item>
数据请求成功,页面没显示,需要将渲染更新重新处理赋值
使用this.$set来重新渲染页面
当数据发生变化的时候使用
// 懒加载 当数据变更的时候才会渲染数据
this.$set(this.listCatchData, current, data);
完整的getList方法
/* 获得对应的信息,需要将分类参数传递给云函数*/
getList(current){
this.$api.get_list({
name:this.tab[current].name
}).then((res)=>{
console.log(res);
const {data} = res;
console.log('请求数据',data);
// this.list = data;
/* 赋值current进行缓存*/
// this.listCatchData[current] = data
// 懒加载 当数据变更的时候才会渲染数据
this.$set(this.listCatchData, current, data);
})
}
list.vue完整代码
<template>
<swiper class="home-swiper" :current="activeIndex" @change="change">
<swiper-item v-for="(item,index) in tab" :key="index" class="swiper-item">
<list-item :list="listCatchData[index]"></list-item>
</swiper-item>
</swiper>
</template>
<script>
/* 因为不符合规范需要手动注册引入*/
import listitem from './list-item.vue'
export default {
components:{'list-item':listitem},
data:function(){
return {
list:[],
// 处理切换的时候内容闪烁的状态(设定缓存状态)
// js 的限制 listCatchData[index] = data
listCatchData: {},
}
},
props:{
tab:Array,
default(){
return [];
},
/* 切换的高亮显示项*/
activeIndex:{
type:Number,
default:0
}
},
/* 当tab发生变化的时候才进行渲染*/
watch:{
tab(newVal) {
if (newVal.length === 0) return;
this.listCatchData = {};
this.getList(this.activeIndex);
},
},
/* onload在页面上,created组件*/
created(){
/* 初始化调用*/
/* tab是没有赋值*/
},
methods:{
change(e){
/* 获得curent属性*/
const {current} = e.detail
/* 获得对应的分类名称*/
console.log(this.tab[current].name)
/* 每次变更将获取到的值传递给getList实现数据变更*/
this.getList(this.tab[current].name)
/* 将current值传给tab顶部组件实现高亮切换联动*/
this.$emit('change',current)
},
/* 获得对应的信息,需要将分类参数传递给云函数*/
getList(current){
this.$api.get_list({
name:this.tab[current].name
}).then((res)=>{
console.log(res);
const {data} = res;
console.log('请求数据',data);
// this.list = data;
/* 赋值current进行缓存*/
// this.listCatchData[current] = data
// 懒加载 当数据变更的时候才会渲染数据
this.$set(this.listCatchData, current, data);
})
}
}
}
</script>
<style lang="less">
.home-swiper{
height: 100%;
.swiper-item{
height:100%;
overflow:hidden;
}
}
</style>
5-15 -1 上拉加载更多(上)
https://ext.dcloud.net.cn/plugin?id=29
使用组件 LoadMore加载更多