vue.js零基础入门到vue项目入土4.0
VUE2.0——VUE3.0系列第四章
1. swiper 组件
1.1 导入swiper模块化
在终端中,输入cnpm i --save swiper,导入swiper模块化
Films.vue:
<template>
<div>
<film-swiper>
<div class="swiper-slide">1111</div>
<div class="swiper-slide">2222</div>
<div class="swiper-slide">3333</div>
</film-swiper>
<div>
二级的声明式导航
</div>
<router-view></router-view>
</div>
</template>
<script>
import filmSwiper from '@/mycomponents/film/FilmSwiper'
export default {
components:{
filmSwiper
}
}
</script>
FilmSwiper.vue:
<template>
<div class="swiper van">
<div class="swiper-wrapper">
<slot></slot>
</div>
<div class="swiper-pagination"></div>
</div>
</template>
<script>
import Swiper from 'swiper/bundle'
import 'swiper/swiper-bundle.css'
export default {
mounted() {
new Swiper('.van', {
pagination: {
el: '.swiper-pagination'
},
loop: this.loop,
autoplay: {
deplay: 2500,
disableOnInteraction: false
}
})
}
}
</script>
1.2 axios 引入遍历数据
Films.vue:
<template>
<div>
<film-swiper :key="datalist.length">
<film-swiper-item v-for="data in datalist" :key="data.id" class="filmswiperitem">
<img :src="data.imgUrl" />
</film-swiper-item>
</film-swiper>
<div>
二级的声明式导航
</div>
<router-view></router-view>
</div>
</template>
<script>
import filmSwiper from '@/mycomponents/film/FilmSwiper'
import filmSwiperItem from '@/mycomponents/film/FilmSwiperItem'
import axios from 'axios'
export default {
data(){
return{
datalist:[]
}
},
mounted(){
axios.get('/banner.json').then(res=>{
console.log(res.data)
this.datalist = res.data.banner
})
},
components:{
filmSwiper,
filmSwiperItem
}
}
</script>
<style lang="scss" scoped>
.filmswiperitem{
img{
width:100%;
height: 200px;
}
}
</style>
2. 选项卡封装
需求:
点击下面的首页,就显示首页组件,点击分类,显示分类组件
点击的时候,当前处于高亮状态,并且文字颜色修改为 red!
思路
TabBar 一个大组件 里面是一个 可以被替换!=> 被TabBarItem(组件) 替换
TabBarItem 这是一个item组件 上面为图标 下面为文本 => 这些也是设置为 为了复用
在item组件 之中 需要使用一个props来接受外面的 path => 用于跳转!
TabBar.vue组件的实现
在component文件下 创建一个tabbar文件夹 => tabbar.vue
#tab-bar 里面包裹一个插槽,用于存放 tabbar-item 组件!
css样式也是书写在这边
使用:在App.vue组件之中引入使用即可,或者在其他之中使用!
<tab-bar>
需要先引入组件 注册组件 使用组件!
TabBar.vue:
<template>
<footer>
<ul>
<router-link to="/films" custom v-slot="{ navigate, isActive }">
<!-- isActive记录是否被点中 -->
<li @click="navigate" :class="isActive?'vanactive':''">电影-{{ isActive }}</li>
</router-link>
<router-link to="/cinemas" active-class="vanacitve" custom v-slot="{ navigate, isActive }">
<li @click="navigate" :class="isActive?'vanactive':''">影院-{{ isActive }}</li>
</router-link>
<router-link to="/center" active-class="vanacitve" custom v-slot="{ navigate, isActive }">
<li @click="navigate" :class="isActive?'vanactive':''">我的-{{ isActive }}</li>
</router-link>
</ul>
</footer>
</template>
<style lang="scss" scoped>
footer{
position: fixed;
bottom: 0;
left: 0;
width:100%;
height: 3.0625rem;
background-color: white;
ul{
display: flex;
li{
flex: 1;
line-height: 3.0625rem;
text-align: center;
}
}
}
</style>
3. 电影导航组件
FilmHeader.vue:
<template>
<ul>
<router-link to="/films/nowplaying" custom v-slot="{ navigate, isActive }">
<!-- isActive记录是否被点中 -->
<li @click="navigate">
<span :class="isActive ? 'vanactive' : ''">正在热映</span>
</li>
</router-link>
<router-link to="/films/comingsoon" custom v-slot="{ navigate, isActive }">
<!-- isActive记录是否被点中 -->
<li @click="navigate">
<span :class="isActive ? 'vanactive' : ''">即将上映</span>
</li>
</router-link>
</ul>
</template>
<style lang="scss" scoped>
ul{
display: flex;
height: 3.0625rem;
line-height: 3.0625rem;
li{
flex: 1;
text-align: center;
}
}
.vanactive{
color: red;
border-bottom: 2px solid red;
}
</style>
Film.vue:
<template>
<div>
<film-swiper :key="datalist.length">
<film-swiper-item v-for="data in datalist" :key="data.id" class="filmswiperitem">
<img :src="data.imgUrl" />
</film-swiper-item>
</film-swiper>
<film-header></film-header>
<router-view></router-view>
</div>
</template>
<script>
import filmSwiper from '@/mycomponents/film/FilmSwiper'
import filmSwiperItem from '@/mycomponents/film/FilmSwiperItem'
import filmHeader from '@/mycomponents/film/FilmHeader'
import axios from 'axios'
export default {
data(){
return{
datalist:[]
}
},
mounted(){
axios.get('/banner.json').then(res=>{
console.log(res.data)
this.datalist = res.data.banner
})
},
components:{
filmSwiper,
filmSwiperItem,
filmHeader
}
}
</script>
<style lang="scss" scoped>
.filmswiperitem{
img{
width:100%;
height: 200px;
}
}
</style>
4. 正在热映取数据
前端调试工具-FeHelper:Postman
获取数据:
<script>
import axios from 'axios'
export default {
mounted() {
axios({
url: 'https://m.maizuo.com/gateway?cityId=440100&pageNum=1&pageSize=10&type=1&k=5877842',
headers: {
'X-Client-Info': '{ "a": "3000", "ch": "1002", "v": "5.2.1", "e": "167643376563166084022273", "bc": "440100" }',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res => {
console.log(res.data)
})
}
</script>
5. 正在热映渲染
App.vue:
<template>
<div>
<tabbar></tabbar>
<!-- 路由容器 -->
<section>
<router-view></router-view>
</section>
</div>
</template>
<script>
import tabbar from'@/mycomponents/Tabbar.vue'
export default {
data() {
return {
}
},
components:{
tabbar
}
}
</script>
<style lang="scss">
*{
margin: 0;
padding: 0;
}
ul{
list-style: none;
}
body{
font-size: 16px;
}
section{
padding-bottom: 3.125rem;
}
</style>
nowplaying.vue:
<template>
<div>
<ul>
<li v-for="data in datalist" :key="data.filmId" @click="handleChangePage(data.filmId)">
<img :src="data.poster" />
<div>
<div class="title">{{ data.name }}</div>
<div class="content">
<div :class="data.grade?'':'hidden'">观众评分:<span style="color:red;">{{ data.grade }}</span></div>
<div class="actors">主演:{{ data.actors |actorsFilter }}</div>
<div>
{{ data.nation }}|{{ data.runtime }}分钟
</div>
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios'
import Vue from 'vue'
Vue.filter('actorsFilter',(data)=>{
if(data===undefined) return '暂无主演'// 防止有undefined的数据导致无法过滤,造成报错
// 把数据的名字给映射出来
return data.map(item=>item.name).join('')
})
export default {
data() {
return {
datalist: []
}
},
mounted() {
axios({
url: 'https://m.maizuo.com/gateway?cityId=440100&pageNum=1&pageSize=10&type=1&k=5877842',
headers: {
'X-Client-Info': '{ "a": "3000", "ch": "1002", "v": "5.2.1", "e": "167643376563166084022273", "bc": "440100" }',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res => {
console.log(res.data.data.films)
this.datalist = res.data.data.films
})
},
methods: {
handleChangePage(id) {
// console.log(id)
// 编程式导航
//detail/1111
// 1-通过路径跳转
// this.$router.push(`/detail/${id}`)
// 2-通过命名路由跳转
this.$router.push({
name: "vanDetail",
params: {
id
}
})
}
}
}
</script>
<style lang="scss" scoped>
ul{
li{
overflow: hidden;
padding: .9375rem;
img{
width: 4.125rem;
height: 5.625rem;
float: left;
}
.title{
font-size: 16px;
}
.content{
font-size: 13px;
color: gray;
.actors{
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 12.5rem;
letter-spacing: 2px;
}
}
}
}
</style>
6. 获取点击电影的详情数据
<template>
<div>
detail
</div>
</template>
<script>
import axios from 'axios'
export default {
created() {
// 拿到的是当前匹配的路由 ,没有r的拿到的是当前匹配到detail的路由
//this.$router拿到的是整个路由对象
//params是$route的属性,用来储存数据的键值对(对象形式,{key:value}),储存很多属性(键值对,属性,属性值)在里面
console.log('created', this.$route.params.id)
// axios 利用id发请求到详情接口,获取详细数据,布局页面
axios({
url: `https://m.maizuo.com/gateway?filmId=${this.$route.params.id}&k=6928203`,
headers: {
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.1","e":"167643376563166084022273","bc":"440100"}',
'X-Host': 'mall.film-ticket.film.info'
}
}).then(res => {
console.log(res.data.data.film)
})
}
}
</script>
7. axios 初次封装
7.1 对于数据请求的封装- 函数return axios
http.js:
// 1- 对于数据请求的封装
import axios from 'axios'
function httpForList() {
return axios({
url: 'https://m.maizuo.com/gateway?cityId=440100&pageNum=1&pageSize=10&type=1&k=5877842',
headers: {
'X-Client-Info': '{ "a": "3000", "ch": "1002", "v": "5.2.1", "e": "167643376563166084022273", "bc": "440100" }',
'X-Host': 'mall.film-ticket.film.list'
}
})
}
function httpForDetail(params) {
return axios({
url: `https://m.maizuo.com/gateway?filmId=${params}&k=6928203`,
headers: {
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.1","e":"167643376563166084022273","bc":"440100"}',
'X-Host': 'mall.film-ticket.film.info'
}
})
}
export default {
httpForDetail,
httpForList
}
7.2 对于数据请求的封装-axios创建的实例http
http.js:
优点:
//可以节省相同的代码,只需把不同的写在各个vue中
//在发请求之前拦截–showLoading
//在成功后拦截–hideLoading
import axios from 'axios'
const http = axios.create({
baseURL: 'https://m.maizuo.com',
timeout: 1000,
headers: {
'X-Client-Info': '{ "a": "3000", "ch": "1002", "v": "5.2.1", "e": "167643376563166084022273", "bc": "440100" }'
}
})
export default http
Detail.vue:
import http from '@/util/http'
export default {
created() {
// 拿到的是当前匹配的路由 ,没有r的拿到的是当前匹配到detail的路由
//this.$router拿到的是整个路由对象
//params是$route的属性,用来储存数据的键值对(对象形式,{key:value}),储存很多属性(键值对,属性,属性值)在里面
console.log('created', this.$route.params.id)
// axios 利用id发请求到详情接口,获取详细数据,布局页面
http({
url: `/gateway?filmId=${this.$route.params.id}&k=6928203`,
headers: {
'X-Host': 'mall.film-ticket.film.info'
}
}).then(res => {
console.log(res.data.data.film)
})
}
}
8. 详情渲染 + Moment.js
Moment.js是一个轻量级的JavaScript时间库,它方便了日常开发中对时间的操作,提高了开发效率。日常开发中,通常会对时间进行下面这几个操作:比如获取时间,设置时间,格式化时间,比较时间等等。
终端引入:cnpm i --save moment
设定moment区域为中国
// require 方式
require('moment/locale/zh-cn')
moment.locale('zh-cn');
// import 方式
import 'moment/locale/zh-cn'
moment.locale('zh-cn');
1)获取当前时间
moment()
2)获取时间戳(以毫秒为单位)
moment().format('x') // 返回值为字符串类型
8.1 引入详情简介
Detail.vue:
<template>
<div v-if="filmInfo">
<!-- 当为假,即数据没来时,就不会创建 -->
<!-- <img :src="filmInfo.poster" /> -->
<div :style="{
backgroundImage: 'url(' + filmInfo.poster + ')'
}" class="poster"></div>
<div class="content">
<div>{{ filmInfo.name }}</div>
<div>
<div class="detail-text">{{ filmInfo.category }}</div>
<div class="detail-text">{{ filmInfo.premiereAt | dateFilter }}上映</div>
<div class="detail-text">{{ filmInfo.nation }}|{{ filmInfo.runtime }}分钟</div>
</div>
</div>
</div>
</template>
<script>
import http from '@/util/http'
import moment from 'moment'
import Vue from 'vue'
moment.locale('zh-cn') // 设置成中午的
Vue.filter('dateFilter', (date) => {
return moment(date * 1000).format('YYYY-MM-DD')
// 时间戳为10,格式化一定要 乘1000
})
export default {
data() {
return {
filmInfo: null
}
},
created() {
// 拿到的是当前匹配的路由 ,没有r的拿到的是当前匹配到detail的路由
//this.$router拿到的是整个路由对象
//params是$route的属性,用来储存数据的键值对(对象形式,{key:value}),储存很多属性(键值对,属性,属性值)在里面
console.log('created', this.$route.params.id)
// axios 利用id发请求到详情接口,获取详细数据,布局页面
http({
url: `/gateway?filmId=${this.$route.params.id}&k=6928203`,
headers: {
'X-Host': 'mall.film-ticket.film.info'
}
}).then(res => {
console.log(res.data.data.film)
this.filmInfo = res.data.data.film
})
}
}
</script>
<style lang="scss" scoped>
.poster {
width: 100%;
height: 13.125rem;
background-position: center;
background-size: cover;
}
.content {
padding: .9375rem;
.detail-text {
color: #797d82;
font-size: 13px;
margin-top: .625rem;
}
}
</style>
8.2 详情简介下拉
<template>
<div v-if="filmInfo">
<!-- 当为假,即数据没来时,就不会创建 -->
<!-- <img :src="filmInfo.poster" /> -->
<div :style="{
backgroundImage: 'url(' + filmInfo.poster + ')'
}" class="poster"></div>
<div class="content">
<div>{{ filmInfo.name }}</div>
<div>
<div class="detail-text">{{ filmInfo.category }}</div>
<div class="detail-text">{{ filmInfo.premiereAt | dateFilter }}上映</div>
<div class="detail-text">{{ filmInfo.nation }}|{{ filmInfo.runtime }}分钟</div>
<div class="detail-text" style="line-height: 15px;" :class="isHidden?'hidden':''">{{ filmInfo.synopsis }}</div>
<div style="text-align:center;"><i class="iconfont" @click="isHidden=!isHidden">v</i></div>
</div>
</div>
</div>
</template>
<script>
import http from '@/util/http'
import moment from 'moment'
import Vue from 'vue'
moment.locale('zh-cn') // 设置成中文的
Vue.filter('dateFilter', (date) => {
return moment(date * 1000).format('YYYY-MM-DD')
// 时间戳为10,格式化一定要 乘1000
})
export default {
data() {
return {
filmInfo: null,
isHidden: true
}
},
created() {
// 拿到的是当前匹配的路由 ,没有r的拿到的是当前匹配到detail的路由
//this.$router拿到的是整个路由对象
//params是$route的属性,用来储存数据的键值对(对象形式,{key:value}),储存很多属性(键值对,属性,属性值)在里面
console.log('created', this.$route.params.id)
// axios 利用id发请求到详情接口,获取详细数据,布局页面
http({
url: `/gateway?filmId=${this.$route.params.id}&k=6928203`,
headers: {
'X-Host': 'mall.film-ticket.film.info'
}
}).then(res => {
console.log(res.data.data.film)
this.filmInfo = res.data.data.film
})
}
}
</script>
<style lang="scss" scoped>
.poster {
width: 100%;
height: 13.125rem;
background-position: center;
background-size: cover;
}
.content {
padding: .9375rem;
.detail-text {
color: #797d82;
font-size: 13px;
margin-top: .625rem;
}
}
.hidden{
overflow: hidden;
height: 30px;
}
</style>
9. 详情轮播
Detail.vue:
<template>
<div v-if="filmInfo">
<!-- 当为假,即数据没来时,就不会创建 -->
<!-- <img :src="filmInfo.poster" /> -->
<div :style="{
backgroundImage: 'url(' + filmInfo.poster + ')'
}" class="poster"></div>
<div class="content">
<div>{{ filmInfo.name }}</div>
<div>
<div class="detail-text">{{ filmInfo.category }}</div>
<div class="detail-text">{{ filmInfo.premiereAt | dateFilter }}上映</div>
<div class="detail-text">{{ filmInfo.nation }}|{{ filmInfo.runtime }}分钟</div>
<div class="detail-text" style="line-height: 15px;" :class="isHidden ? 'hidden' : ''">{{
filmInfo.synopsis
}}</div>
<div style="text-align:center;">
<i
class="iconfont"
@click="isHidden = !isHidden">v</i>
</div>
</div>
<!-- 演职人员 -->
<div>
<div>演职人员</div>
<detail-swiper :perview="3.5" name="actors">
<detail-swiper-item v-for="(data, index) in filmInfo.actors" :key="index">
<div :style="{
backgroundImage: 'url(' + data.avatarAddress + ')'
}" class="avatar"></div>
<div style="text-align: center;font-size: 12px;">{{ data.name }}</div>
<div style="text-align: center;font-size: 13px;">{{ data.role }}</div>
</detail-swiper-item>
</detail-swiper>
</div>
<!-- 剧照 -->
<div>
<div>剧照</div>
<detail-swiper :perview="2" name="photos">
<detail-swiper-item v-for="(data, index) in filmInfo.photos" :key="index">
<div :style="{
backgroundImage: 'url(' + data + ')'
}" class="avatar"></div>
</detail-swiper-item>
</detail-swiper>
</div>
</div>
</div>
</template>
<script>
import http from '@/util/http'
import moment from 'moment'
import Vue from 'vue'
import detailSwiper from '@/mycomponents/detail/DetailSwiper.vue'
import detailSwiperItem from '@/mycomponents/detail/DetailSwiperItem.vue'
moment.locale('zh-cn') // 设置成中文的
Vue.filter('dateFilter', (date) => {
return moment(date * 1000).format('YYYY-MM-DD')
// 时间戳为10,格式化一定要 乘1000
})
export default {
data() {
return {
filmInfo: null,
isHidden: true
}
},
components: {
detailSwiper,
detailSwiperItem
},
created() {
// 拿到的是当前匹配的路由 ,没有r的拿到的是当前匹配到detail的路由
//this.$router拿到的是整个路由对象
//params是$route的属性,用来储存数据的键值对(对象形式,{key:value}),储存很多属性(键值对,属性,属性值)在里面
console.log('created', this.$route.params.id)
// axios 利用id发请求到详情接口,获取详细数据,布局页面
http({
url: `/gateway?filmId=${this.$route.params.id}&k=6928203`,
headers: {
'X-Host': 'mall.film-ticket.film.info'
}
}).then(res => {
console.log(res.data.data.film)
this.filmInfo = res.data.data.film
})
}
}
</script>
<style lang="scss" scoped>
.poster {
width: 100%;
height: 13.125rem;
background-position: center;
background-size: cover;
}
.content {
padding: .9375rem;
.detail-text {
color: #797d82;
font-size: 13px;
margin-top: .625rem;
}
}
.hidden {
overflow: hidden;
height: 30px;
}
.avatar {
width: 100%;
height: 5.3125rem;
background-position: center;
background-size: cover;
}
</style>
DetailSwiper.vue:
<template>
<div class="swiper" :class="name">
<div class="swiper-wrapper">
<slot></slot>
</div>
</div>
</template>
<script>
import Swiper from 'swiper/bundle'
import 'swiper/swiper-bundle.css'
export default {
props: {
perview: {
type: Number,
default: 1
},
name: {
type: String,
default: 'van'
}
},
mounted() {
new Swiper('.' + this.name, {
slidesPerView: this.perview,
spaceBetween: 30,
freeMode: true
})
}
}
</script>
10. 详情Header
10.1 详情H-头部组件
头部组件吸顶返回
Detail.vue:
<template>
<div v-if="filmInfo">
<!-- 当为假,即数据没来时,就不会创建 -->
<!-- <img :src="filmInfo.poster" /> -->
<detail-header>
{{ filmInfo.name }}
</detail-header>
<div :style="{
backgroundImage: 'url(' + filmInfo.poster + ')'
}" class="poster"></div>
<div class="content">
<div>{{ filmInfo.name }}</div>
<div>
<div class="detail-text">{{ filmInfo.category }}</div>
<div class="detail-text">{{ filmInfo.premiereAt | dateFilter }}上映</div>
<div class="detail-text">{{ filmInfo.nation }}|{{ filmInfo.runtime }}分钟</div>
<div class="detail-text" style="line-height: 15px;" :class="isHidden ? 'hidden' : ''">{{
filmInfo.synopsis
}}</div>
<div style="text-align:center;">
<i
class="iconfont"
@click="isHidden = !isHidden">v</i>
</div>
</div>
<!-- 演职人员 -->
<div>
<div>演职人员</div>
<detail-swiper :perview="3.5" name="actors">
<detail-swiper-item v-for="(data, index) in filmInfo.actors" :key="index">
<div :style="{
backgroundImage: 'url(' + data.avatarAddress + ')'
}" class="avatar"></div>
<div style="text-align: center;font-size: 12px;">{{ data.name }}</div>
<div style="text-align: center;font-size: 13px;">{{ data.role }}</div>
</detail-swiper-item>
</detail-swiper>
</div>
<!-- 剧照 -->
<div>
<div>剧照</div>
<detail-swiper :perview="2" name="photos">
<detail-swiper-item v-for="(data, index) in filmInfo.photos" :key="index">
<div :style="{
backgroundImage: 'url(' + data + ')'
}" class="avatar"></div>
</detail-swiper-item>
</detail-swiper>
</div>
</div>
</div>
</template>
<script>
import http from '@/util/http'
import moment from 'moment'
import Vue from 'vue'
import detailHeader from '@/mycomponents/detail/DetailHeader.vue'
import detailSwiper from '@/mycomponents/detail/DetailSwiper.vue'
import detailSwiperItem from '@/mycomponents/detail/DetailSwiperItem.vue'
moment.locale('zh-cn') // 设置成中文的
Vue.filter('dateFilter', (date) => {
return moment(date * 1000).format('YYYY-MM-DD')
// 时间戳为10,格式化一定要 乘1000
})
export default {
data() {
return {
filmInfo: null,
isHidden: true
}
},
components: {
detailSwiper,
detailSwiperItem,
detailHeader
},
created() {
// 拿到的是当前匹配的路由 ,没有r的拿到的是当前匹配到detail的路由
//this.$router拿到的是整个路由对象
//params是$route的属性,用来储存数据的键值对(对象形式,{key:value}),储存很多属性(键值对,属性,属性值)在里面
console.log('created', this.$route.params.id)
// axios 利用id发请求到详情接口,获取详细数据,布局页面
http({
url: `/gateway?filmId=${this.$route.params.id}&k=6928203`,
headers: {
'X-Host': 'mall.film-ticket.film.info'
}
}).then(res => {
console.log(res.data.data.film)
this.filmInfo = res.data.data.film
})
}
}
</script>
<style lang="scss" scoped>
.poster {
width: 100%;
height: 13.125rem;
background-position: center;
background-size: cover;
}
.content {
padding: .9375rem;
.detail-text {
color: #797d82;
font-size: 13px;
margin-top: .625rem;
}
}
.hidden {
overflow: hidden;
height: 30px;
}
.avatar {
width: 100%;
height: 5.3125rem;
background-position: center;
background-size: cover;
}
</style>
DetailHeader.vue:
<template>
<div class="header">
<i class="iconfont" @click="handleBack">
<
</i>
<slot></slot>
</div>
</template>
<script>
export default{
methods:{
handleBack() {
this.$router.back() //返回上一个页面
}
}
}
</script>
<style lang="scss" scoped>
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 2.75rem;
line-height: 2.75rem;
background-color: white;
text-align: center;
i {
font-size: 30px;
position: fixed;
left: .625rem;
top: 0px;
height: 2.75rem;
line-height: 2.75rem;
}
}
</style>
10.2 详情H-自定义指令
通过window.onscroll,if判断,大于一定距离显示详情,
最后记得小于一定距离的时候要清空
得销毁声明周期:unbind
Detail.vue:
<template>
<div v-if="filmInfo">
<!-- 当为假,即数据没来时,就不会创建 -->
<!-- <img :src="filmInfo.poster" /> -->
<detail-header v-scroll = "50">
{{ filmInfo.name }}
</detail-header>
<div :style="{
backgroundImage: 'url(' + filmInfo.poster + ')'
}" class="poster"></div>
<div class="content">
<div>{{ filmInfo.name }}</div>
<div>
<div class="detail-text">{{ filmInfo.category }}</div>
<div class="detail-text">{{ filmInfo.premiereAt | dateFilter }}上映</div>
<div class="detail-text">{{ filmInfo.nation }}|{{ filmInfo.runtime }}分钟</div>
<div class="detail-text" style="line-height: 15px;" :class="isHidden ? 'hidden' : ''">{{
filmInfo.synopsis
}}</div>
<div style="text-align:center;">
<i class="iconfont" @click="isHidden = !isHidden">v</i>
</div>
</div>
<!-- 演职人员 -->
<div>
<div>演职人员</div>
<detail-swiper :perview="3.5" name="actors">
<detail-swiper-item v-for="(data, index) in filmInfo.actors" :key="index">
<div :style="{
backgroundImage: 'url(' + data.avatarAddress + ')'
}" class="avatar"></div>
<div style="text-align: center;font-size: 12px;">{{ data.name }}</div>
<div style="text-align: center;font-size: 13px;">{{ data.role }}</div>
</detail-swiper-item>
</detail-swiper>
</div>
<!-- 剧照 -->
<div>
<div>剧照</div>
<detail-swiper :perview="2" name="photos">
<detail-swiper-item v-for="(data, index) in filmInfo.photos" :key="index">
<div :style="{
backgroundImage: 'url(' + data + ')'
}" class="avatar"></div>
</detail-swiper-item>
</detail-swiper>
</div>
</div>
</div>
</template>
<script>
import http from '@/util/http'
import moment from 'moment'
import Vue from 'vue'
import detailHeader from '@/mycomponents/detail/DetailHeader.vue'
import detailSwiper from '@/mycomponents/detail/DetailSwiper.vue'
import detailSwiperItem from '@/mycomponents/detail/DetailSwiperItem.vue'
moment.locale('zh-cn') // 设置成中文的
Vue.filter('dateFilter', (date) => {
return moment(date * 1000).format('YYYY-MM-DD')
// 时间戳为10,格式化一定要 乘1000
})
Vue.directive("scroll", {
inserted(el,binding) {
// console.log(binding.value)
el.style.display = 'none'
window.onscroll = () => {
if ((document.documentElement.scrollTop || document.body.scrollTop) > binding.value) {
el.style.display = 'block'
} else {
el.style.display = 'none'
}
}
},
//销毁执行的
unbind() {
window.onscroll = null
}
})
export default {
data() {
return {
filmInfo: null,
isHidden: true
}
},
components: {
detailSwiper,
detailSwiperItem,
detailHeader
},
created() {
// 拿到的是当前匹配的路由 ,没有r的拿到的是当前匹配到detail的路由
//this.$router拿到的是整个路由对象
//params是$route的属性,用来储存数据的键值对(对象形式,{key:value}),储存很多属性(键值对,属性,属性值)在里面
console.log('created', this.$route.params.id)
// axios 利用id发请求到详情接口,获取详细数据,布局页面
http({
url: `/gateway?filmId=${this.$route.params.id}&k=6928203`,
headers: {
'X-Host': 'mall.film-ticket.film.info'
}
}).then(res => {
console.log(res.data.data.film)
this.filmInfo = res.data.data.film
})
}
}
</script>
<style lang="scss" scoped>
.poster {
width: 100%;
height: 13.125rem;
background-position: center;
background-size: cover;
}
.content {
padding: .9375rem;
.detail-text {
color: #797d82;
font-size: 13px;
margin-top: .625rem;
}
}
.hidden {
overflow: hidden;
height: 30px;
}
.avatar {
width: 100%;
height: 5.3125rem;
background-position: center;
background-size: cover;
}
</style>
11. 影院组件渲染
Cinemas.vue
<template>
<div>
<ul>
<li v-for="data in cinemaList" :key="data.cinemaId">
<div class="left">
<div class="cinema_name">{{ data.name }}</div>
<div class="cinema_text">{{ data.address }}</div>
</div>
<div class="right">
<div style="color: red;">¥{{ data.lowPrice / 100 }}起</div>
</div>
</li>
</ul>
</div>
</template>
<script>
import http from '@/util/http'
export default {
data() {
return {
cinemaList: []
}
},
mounted() {
http({
url: '/gateway?cityId=440100&ticketFlag=1&k=9458654',
headers: {
'X-Host': 'mall.film-ticket.cinema.list'
}
}).then(res => {
// console.log(res.data)
this.cinemaList = res.data.data.cinemas
})
}
}
</script>
<style lang="scss" scoped>
li {
padding: .9375rem;
display: flex;
justify-content: space-between;
.left {
width: 13.25rem;
}
.right{
font-size: 15px;
}
.cinema_name {
font-size: 15px;
}
.cinema_text {
color: #797d82;
font-size: 12px;
margin-top: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>
12. 影院组件优化
1 .js单线程,js线程会阻塞UI线程,所以当脚本执行时间过长就会阻塞渲染,导致页面卡顿
2 .Vue数据变化会有追踪机制,只会更新自己组件的数据,而React会全部重新渲染组件
3 .函数式组件和普通的对象类型的组件不同,函数式组件render生成的式普通的Vnode,不会有递归子组件的过程.怎么感觉是函数才会自动递归组件
4 .在patch过程中,如果遇到一个节点组件,就会递归执行子组件的初始化过程.更react是相反的
5 .函数式组件没有状态,没有响应式数据,生命周期钩子这些东西,可以把他当成普通组件模板中的一部分DOM剥离出来,通过函数方式渲染,是一种DOM层面的渲染
.box {
// height: 38.625rem;// 但是rem一直比的是宽度的百分比,所以高度在不同窗口下是会有问题的,所以要动态结算高度
overflow: hidden;
position:relative;
// 修正滚动条的的位置
}
//动态结算高度,视口高度-底部选项卡的高度=中心页面滚动的高度
this.height = document.documentElement.clientHeight-选项卡高度
this.height = document.documentElement.clientHeight- document.querySelector('footer').offsetHeight + 'px'
12.1 BetterScroll
https://better-scroll.github.io/docs/zh-CN/
主要完成的功能需要包含Better-Scroll实现页面中拖动滚动、拉动属性等功能
终端引入:cnpm i --save better-scroll
12.2 $nextTick
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
什么意思?
我们可以理解成,Vue 在更新 DOM 时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新
为什么要有nexttick
{{num}}
for(let i=0; i<100000; i++){
num = i
}
如果没有 nextTick 更新机制,那么 num 每次更新值都会触发视图更新(上面这段代码也就是会更新10万次视图),有了nextTick机制,只需要更新一次,所以nextTick本质是一种优化策略
13. elementUI 组件库
1.饿了么UED团队推出的vue前端框架:
(1) PC框架:(element UI)
https://element.eleme.cn/#/zh-CN
Element-Ui简介
element 是基于 vue 实现的一套不依赖业务的 UI 组件库,提供了丰富的PC端组件,减少用户对常用组件的封装,降低了开发的难易程度。
vue与Element-Ui的关系
- Element-Ui是基于vue封装的组件库,简化了常用组件的封装,提高了重用性原则;
- vue是一个渐进式框架,Element-Ui是组件库;
PCApp.vue:
<template>
<el-container style="height: 500px; border: 1px solid #eee">
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu :default-openeds="['1','2']">
<el-submenu :index="data.id + ''" v-for="data in sideList" :key="data.id">
<template slot="title"><i class="el-icon-message"></i>{{ data.title }}</template>
<el-menu-item :index="item.id + ''" v-for="item in data.children
" :key="item.id">
{{ item.title }}
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 12px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>修改</el-dropdown-item>
<el-dropdown-item>退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span>{{ myname }}</span>
</el-header>
<el-main>
<el-table :data="tableData">
<el-table-column prop="date" label="日期" width="140">
</el-table-column>
<el-table-column prop="name" label="姓名" width="120">
</el-table-column>
<el-table-column prop="address" label="地址">
</el-table-column>
</el-table>
</el-main>
</el-container>
</el-container>
</template>
<script>
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import Vue from 'vue';
Vue.use(ElementUI);
export default {
methods: {
handlePrimary() {
console.log("primary")
}
},
data() {
return {
myname:"vanwot",
sideList: [
{
id: 1,
title: "用户管理",
children: [
{
id: 2,
title: "用户列表"
},
{
id: 3,
title: "用户权限"
},
]
},
{
id: 4,
title: "权限管理",
children: [
{
id: 5,
title: "角色列表"
},
{
id: 6,
title: "角色"
},
]
}
],
tableData: [
{
title:"van",
address:"magua",
date:"2021-05-01"
},
{
title:"wot",
address:"beijin",
date:"2021-05-02"
},
{
title:"xiaoming",
address:"hangzhou",
date:"2021-05-03"
},
]
}
}
};
</script>
<style>
.el-header {
background-color: #B3C0D1;
color: #333;
line-height: 60px;
}
.el-aside {
color: #333;
}
</style>
14. vant 引入
https://youzan.github.io/vant-weapp/#/home
安装:
npm i vant -S(简写)
npm install vant --save(完整写法)
14.1 全局引入
main.js:
import Vant from 'vant'
import 'vant/lib/vant-css/index.css'
Vue.use(Vant)
<template>
<div>
<van-nav-bar title="影院" ref="navbar">
<template #left>
上海<van-icon name="arrow-down" />
</template>
<template #right>
<van-icon name="search" size="20" color="black"/>
</template>
</van-nav-bar>
<div class="box" :style="{
height: height
}">
<ul>
<li v-for="data in cinemaList" :key="data.cinemaId">
<div class="left">
<div class="cinema_name">{{ data.name }}</div>
<div class="cinema_text">{{ data.address }}</div>
</div>
<div class="right">
<div style="color: red;">¥{{ data.lowPrice / 100 }}起</div>
</div>
</li>
</ul>
</div>
</div>
</template>
<script>
import http from '@/util/http'
import BetterScroll from 'better-scroll'
export default {
data() {
return {
cinemaList: [],
height: '0px'
}
},
mounted() {
// console.log(this.$refs.navbar.$el.offsetHeight)
// 动态结算高度,视口高度-底部选项卡的高度-顶部导航栏的高度=中心页面滚动的高度
this.height =
document.documentElement.clientHeight
- this.$refs.navbar.$el.offsetHeight
-document.querySelector("footer").offsetHeight + 'px'
http({
url: '/gateway?cityId=440100&ticketFlag=1&k=9458654',
headers: {
'X-Host': 'mall.film-ticket.cinema.list'
}
}).then(res => {
// console.log(res.data)
this.cinemaList = res.data.data.cinemas
this.$nextTick(() => {// 保证dom上树,保证BetterScroll回调,实现初始化滚动
new BetterScroll('.box', {
scrollbar: {
fade: true
}
})
})
})
}
}
</script>
<style lang="scss" scoped>
li {
padding: .9375rem;
display: flex;
justify-content: space-between;
.left {
width: 13.25rem;
}
.right {
font-size: 15px;
}
.cinema_name {
font-size: 15px;
}
.cinema_text {
color: #797d82;
font-size: 12px;
margin-top: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.box {
// height: 38.625rem;// 但是rem一直比的是宽度的百分比,所以高度在不同窗口下是会有问题的
overflow: hidden;
position: relative;
// 修正滚动条的的位置
}
</style>
14.2 按需引入
首先,安装babel-plugin-import
npm i babel-plugin-import -D(简写)
npm install babel-plugin-import --save-dev(完整写法)
然后,在.babelrc文件中
"plugins": [
"transform-vue-jsx",
"transform-runtime",
<!--这个是添加的-->
["import",{"libraryName":"vant","style":true}]
]
最后,在main.js中
import {Button} from 'vant'
Vue.use(Button)
// 引入的多的话
import { Button, Row, Col } from 'vant'
Vue.use(Button).use(Row).use(Col)
14.3 在页面中单独使用组件
import { Button, Row, Col } from 'vant'
export default{
components: {
[Button.name]:Button // 有的组件有专门的引入方式,比如Dialog,可以看看
}
}
15. vant 应用 点击图片预览
通过查询文档,复制粘贴,增加点击事件,如预览效果 ,
通过传入index值,获取每次页面打开时显示的图片页
Detail.vue:
<template>
<div v-if="filmInfo">
<!-- 当为假,即数据没来时,就不会创建 -->
<!-- <img :src="filmInfo.poster" /> -->
<detail-header v-scroll="50">
{{ filmInfo.name }}
</detail-header>
<div :style="{
backgroundImage: 'url(' + filmInfo.poster + ')'
}" class="poster"></div>
<div class="content">
<div>{{ filmInfo.name }}</div>
<div>
<div class="detail-text">{{ filmInfo.category }}</div>
<div class="detail-text">{{ filmInfo.premiereAt | dateFilter }}上映</div>
<div class="detail-text">{{ filmInfo.nation }}|{{ filmInfo.runtime }}分钟</div>
<div class="detail-text" style="line-height: 15px;" :class="isHidden ? 'hidden' : ''">{{
filmInfo.synopsis
}}</div>
<div style="text-align:center;">
<i class="iconfont" @click="isHidden = !isHidden">v</i>
</div>
</div>
<!-- 演职人员 -->
<div>
<div>演职人员</div>
<detail-swiper :perview="3.5" name="actors">
<detail-swiper-item v-for="(data, index) in filmInfo.actors" :key="index">
<div :style="{
backgroundImage: 'url(' + data.avatarAddress + ')'
}" class="avatar"></div>
<div style="text-align: center;font-size: 12px;">{{ data.name }}</div>
<div style="text-align: center;font-size: 13px;">{{ data.role }}</div>
</detail-swiper-item>
</detail-swiper>
</div>
<!-- 剧照 -->
<div>
<div>剧照</div>
<detail-swiper :perview="2" name="photos">
<detail-swiper-item v-for="(data, index) in filmInfo.photos" :key="index">
<div :style="{
backgroundImage: 'url(' + data + ')'
}" class="avatar" @click="handlePreview(index)"></div>
</detail-swiper-item>
</detail-swiper>
</div>
</div>
</div>
</template>
<script>
import http from '@/util/http'
import moment from 'moment'
import Vue from 'vue'
import detailHeader from '@/mycomponents/detail/DetailHeader.vue'
import detailSwiper from '@/mycomponents/detail/DetailSwiper.vue'
import detailSwiperItem from '@/mycomponents/detail/DetailSwiperItem.vue'
import { ImagePreview } from 'vant';
moment.locale('zh-cn') // 设置成中文的
Vue.filter('dateFilter', (date) => {
return moment(date * 1000).format('YYYY-MM-DD')
// 时间戳为10,格式化一定要 乘1000
})
Vue.directive("scroll", {
inserted(el, binding) {
// console.log(binding.value)
el.style.display = 'none'
window.onscroll = () => {
if ((document.documentElement.scrollTop || document.body.scrollTop) > binding.value) {
el.style.display = 'block'
} else {
el.style.display = 'none'
}
}
},
//销毁执行的
unbind() {
window.onscroll = null
}
})
export default {
data() {
return {
filmInfo: null,
isHidden: true
}
},
components: {
detailSwiper,
detailSwiperItem,
detailHeader
},
methods: {
handlePreview(index) {
ImagePreview({
images: this.filmInfo.photos,
startPosition: index,
closeable:true,
closeIconPosition: 'top-left'
})
}
},
created() {
// 拿到的是当前匹配的路由 ,没有r的拿到的是当前匹配到detail的路由
//this.$router拿到的是整个路由对象
//params是$route的属性,用来储存数据的键值对(对象形式,{key:value}),储存很多属性(键值对,属性,属性值)在里面
console.log('created', this.$route.params.id)
// axios 利用id发请求到详情接口,获取详细数据,布局页面
http({
url: `/gateway?filmId=${this.$route.params.id}&k=6928203`,
headers: {
'X-Host': 'mall.film-ticket.film.info'
}
}).then(res => {
console.log(res.data.data.film)
this.filmInfo = res.data.data.film
})
}
}
</script>
<style lang="scss" scoped>
.poster {
width: 100%;
height: 13.125rem;
background-position: center;
background-size: cover;
}
.content {
padding: .9375rem;
.detail-text {
color: #797d82;
font-size: 13px;
margin-top: .625rem;
}
}
.hidden {
overflow: hidden;
height: 30px;
}
.avatar {
width: 100%;
height: 5.3125rem;
background-position: center;
background-size: cover;
}
</style>