VUE项目--卖座app

VUE项目--卖座app,第1张

一.由于做移动端,所以先根据设计稿设置好设置rem

二、做轮播图
2.使用swiper引入轮播图
1)现在flims文件下自定义film-swiper组件,来显示大轮播,注意加上key值,不然会加载过早。
2)自定义一个film-swiper-item组件包裹着轮播数据


      
      
         
        

创建film-swiper和film-swiper-item组件

film-swiper-ltem组件

注意要引入swiper插件

导入组件flims页面

3.使用axios获取轮播图信息

这个json文件是本地保存的,为了就是方便设置轮播图

实现效果

三、做底部选项卡
1.先创建Tabbar组件,使用声明式导航,点击转去点击页面,active-class是动态给节点加入样式,当点击时候就给class加上样式


      
      
        
          电影
        
      
      
        
          影院
        
      
      
        
          我的
        
      
    

在app页面上引入Tabbar组件


  
    
    
    

效果图

四、电影导航组件
1.创建swiper-header组件

 
        
        
          正在热映
        
      
       
        
          即将上映
        
      
    

整体效果

五、正在热映的数据获取
由于网站没有跨域限制,所以可以拿取数据

不过需要设置请求头

所以使用axios进行封装

const http = axios.create({
  baseURL: 'https://m.maizuo.com',  //每次访问就是maizuo.com
  timeout: 10000,   //10秒超时
  headers: {
    'X-Client-Info':
      '{"a":"3000","ch":"1002","v":"5.2.0","e":"16481780177384775358676993","bc":"440100"}'
  }
})

可以获取到数据
接下来就是在正在热映的页面使用获取回来的数据
先把得到的电影flims数据给datalist

http({
      url: `/gateway?cityId=${this.$store.state.cityId}&pageNum=1&pageSize=10&type=1&k=7388950`,
      headers: {
        'X-Host': 'mall.film-ticket.film.list'
      }
    }).then((res) => {
      this.datalist = res.data.data.films
    })
      
        
        {{ data.name }}
        
          
            观众评分:{{ data.grade }} 
          

不过注意这个这里的演员数据是以对象形式存在,不能直接使用

所以必须给数据加上过滤器


  
          主演:{{ data.actors | actorsFilter }}

使用map把item.name映射给item

Vue.filter('actorsFilter', (data) => {
  return data.map((item) => item.name).join(' ')
})

得到的效果

六、详情页的制作
点击数据之后跳转到详情页面,并且把filmid传过去,确定是那个页面被打开

  this.$router.push({
        name: 'bee',
        params: {
          id
        }
      })

在detail页面上, $route当前匹配的路由; axios利用id发请求到详情接口,获取详细数据,布局页面

    http({
      url: `/gateway?filmId=${this.$route.params.id}&k=106604`,
      headers: {
        'X-Host': ' mall.film-ticket.film.info'
      }
    }).then((res) => {
      console.log(res.data.data.film)
      this.filmInfo = res.data.data.film
    })

使用的数据进行页面的布局



    

上映的时间,不过的到的是秒数 需要二次转换加一个过滤器


 
        {{ filmInfo.category }} 
        {{ filmInfo.premiereAt | dataFilter }} 
        
          {{ filmInfo.nation }} | {{ filmInfo.runtime }}分钟
        
        

          {{ filmInfo.synopsis }}
        
        
             //是否显示和隐藏的功能
        
      


之后得到页面是这样

演职人员:
使用轮播制作 数据的话直接套用上面获取的数据 和之前的swiper 一样的方法
不过注意下面的剧照,也需要使用轮播图,这时候就是两个页面同时使用一个轮播图组件,所以为了避免冲突要使用动态名,只需要把传过去的名改变就可以避免轮播冲突
swiper虽然用的同一组件,但是给的名称name不一样,不会导致轮播冲突


new Swiper('.' + this.name, {
      slidesPerView: this.perview,  /* 一页显示多少个 支持小数 */
      spaceBetween: 30,   /* 间隔多少 */
      freeMode: true

 
        演职人员
        
          
            
            
              {{ data.name }}
            
            
              {{ data.role }}
            
          
        
      

显示的效果:

做一个滚动50距离就显示头部的信息
创建swiper-header组件

 
    
    
  

点击小箭头就返回原来的页面

 methods: {
    handleBack () {
      this.$router.back()
    }

在detail中插入swiper-header组件


 
    
      {{ filmInfo.name }}
    

自定也组件scroll


/* 创建往下滚动时候,出现顶部的返回栏 el得到原生dom节点(谁用谁绑定) */
Vue.directive('scroll', {
  inserted (el, binding) {
    /* 把原生dom的节点一开始隐藏 */
    el.style.display = 'none'
    window.onscroll = () => {
      /* 兼容性问题 */
      if (
        /* 当滚动距离大于50时,就开始显示   binding.value是传过来的值 50*/
        (document.documentElement.scrollTop || document.body.scrollTop) >
        binding.value
      ) {
        el.style.display = 'block'
      } else {
        el.style.display = 'none'
      }
    }
  },
  
/* 指令也是有声明周期的 当退出时把指令销毁 */
  unbind () {
    window.onscroll = null
  }
})

页面效果

预览图片功能
通过npm安装vant

使用vant实现图片预览功能,引入
ImagePreview

给剧照加一个点击事件

 @click="handlePreview(index)

  handlePreview (index) {
      ImagePreview({
        images: this.filmInfo.photos,  
/* 预览的图片 */
        startPosition: index,
/* 传入的初始位置 点击谁 显示谁 */
        closeable: true,
/* 关闭按钮 */
        closeIconPosition: 'top-left'
 /* 关闭按钮的位置 */
      })
    }

效果图

影院的组件
先使用axios获取数据

http({
      url: `/gateway?cityId=${this.$store.state.cityId}&ticketFlag=1&k=9755784`,
      headers: {
        "X-Host": "mall.film-ticket.cinema.list",
      },
    }).then((res) => {
      this.cinemaList = res.data.data.cinemas;
}}

        
          
            {{ data.name }}
            {{ data.address }}
          
          
            ¥{{ data.lowPrice / 100 }}起
          
        
      

显示的效果:

使用vant插件,渲染出影院页面的头部

所以使用vant创建左边的地方和右边搜索框



      
      
    

数据懒加载
使用vant里面的list实现数据懒加载功能
list组件说明:
List 组件通过 loading 和 finished 两个变量控制加载状态,当组件滚动到底部时,会触发 load 事件并将 loading 设置成 true。此时可以发起异步 *** 作并更新数据,数据更新完毕后,将 loading 设置成 false 即可。若数据已全部加载完毕,则直接将 finished 设置成 true 即可。

在nowplaying中加入


    

注意要让loading和finish一开始状态是false。loading为false的话,可以加载;finish为false的话,表示并没有结束,实现懒加载功能;

data () {
    return {
      loading: false,
      finished: false,
      current: 1,  //返回的页数
      total: 0    
    }

加入list之后会直接显示一次到底了

解决的方法,从手册可知道

所以加上一个immediate-check 注意给的是boolean值,所以在前面加上:

 :immediate-check="false"  //第一次默认不检查


问题解决了

到底之后再次使用axios请求数据

  http({
        url: `/gateway?cityId=${this.$store.state.cityId}&pageNum=${this.current}&pageSize=10&type=1&k=7388950`,   //currtent 是获取的页数  city是传过来的城市id 后面有说
        headers: {
          'X-Host': 'mall.film-ticket.film.list'   //获取列表数据
        }
      }).then((res) => {
        this.datalist = [...this.datalist, ...res.data.data.films]   //取回来的数据进行合并处理 老数组+新获取的数组
        
        /* 记得让loding设置成false */
        this.loading = false
      })

这时候就会发现 到底之后,loading一直为false ,会不断获取向后端获取数据。但是已经没有数据了,会产生一个死循环


注意到获取回来的数据有一个total值,存放至有多少影片

所以解决方法是,在onload开头加一个if条件,当datalist.length等于total并且不等于0时,就把finish状态设置true,禁用懒加载功能。

onLoad () {
      if (this.datalist.length === this.total && this.total != 0) {
        this.finished = true
        return
      }

loading加载
loading功能,就是每次切换页面时候出现小圆圈等待数据加载
还是使用vant的Toast实现:

在create的时候添加Toast,
forbidClick 属性可以禁用背景点击
duration展示时长(ms),值为 0 时,toast 不会消失

Toast.loading({
      message: '加载中...',
      forbidClick: true,
      duration: 0 //
    })

当axios请求完数据时候把Toast清除掉

 Toast.clear()

axios拦截器
可以在请求之前拦截和在请求之后拦截
所以接合axios拦截器的特点,我们可以把Toast写在axios里面,每次有组件调用axios请求数据,都会实现Toast功能

// 在请求之前拦截  --showloding
http.interceptors.request.use(
  function (config) {
    Toast.loading({
      message: '加载中...',
      forbidClick: true,
      duration: 0
    })
    return config
  },
  function (error) {
    return Promise.reject(error)
  }
)

// 在请求之后拦截 
http.interceptors.response.use(
  function (response) {
    // 隐藏 清空taost
    Toast.clear()
    return response
  },
  function (error) {
    // 隐藏 清空taost
    Toast.clear()
    return Promise.reject(error)
  }
)

城市组件

之前在cinema组件用过navbar,有个城市的小按钮
找文档查看一下点击事件
引入city组件和search组件

  handleLeft () {
      this.$router.push('/city')
    },

  handleRight () {
   this.$router.push('/cinemas/search')
    }

同样使用axios获取数据

 http({
      url: '/gateway?k=9757741',
      headers: {
        'X-Host': 'mall.film-ticket.city.list'
      }
    }).then(res => {
      this.cityList = this.renderCity(res.data.data.cities) /* 使用函数redercity获取26个字母开头的数组 */

rederCity函数,先把26个字母遍历出来,并插入letterList里面

 renderCity (list) {
      const letterList = []
      const cityList = []
      /* 遍历出26个英文字母 */
      for (let i = 65; i < 91; i++) {
 letterList.push(String.fromCharCode(i))
      }

由于的数据里面包含地方的拼音

将leeterlist中的26个字母遍历给letter,再将list里面得到的数据城市拼音的元素全部拿出来,截取第一个元素(拼音的开头字母),将其变成大写  使用es5的过滤filter获取每次过滤的到的结果(把a的打印出来;把b的打印出来....);这就得到26个数组

      letterList.forEach(Letter => {
        const newlist = list.filter(item => item.pinyin.substring(0, 1).toUpperCase() === Letter)

得到的newlist是26个字母的数组

需要注意的是没有o和v开头的城市
所以需要判断一下

  // 只有长度大于0才可以遍历
        newlist.length > 0 && cityList.push({
          type: Letter,
          list: newlist

得到22个城市数组

在页面上渲染,使用到了vant上的

            
            
                
                
                
            

创建的结果如图所示

接下来就是点击城市,需要把点击的数据拿出来,所以在加入事件

 
            

 handleClick (item) {
      console.log(item.name, item.cityId)
}

点击之后,获取到了点击的城市和城市id

vuex --状态管理模式
项目中城市信息,来分类各城市影院和影片,所以引入vuex来共享管理city信息
在index文件中写入初始状态

 new Vuex.Store({
  /* 公共状态 */
  state: {
    cityId: '440100',
    cityName: '广州',
    cinemaList: [],
    isTabbarShow: true
  },

这时候store已经实例化了,可以直接使用this.store就可以访问store了。所以在任何页面中使用this.store.state.cityName城市名的公共状态
所以当点击列表的城市名称时,可以做到切换城市

handleClick (item) {
      // vuex -状态管理模式  让公共状态的cityName改变成点击的城市item.name 
this.$store.state.cityName = item.name;
this.$router.back() ;//返回上一次页面
}

成功的切换城市名称

但是注意虽然这样很简单,但是有隐患,当每个组件都可以修改state里面的公共状态时,一旦发生错误,就无法知道在哪里使用导致错误,所以得需要有监督
所以就在每次修改时候,都经过一个mutation来进行监控管理,防止状态被恶意修改

 

vue ComponentMutationDevtoolsState

使用Devtools工具可以查看监控状态,可视化工具,

所以当修改状态时候,提交到mucation中,把item.name传过去,在mucation中修改,这是mucation的同步修改

 this.$store.commit('changeCityName', item.name)    this.$store.commit('changeCityId', item.cityId)
this.$router.back()
mutations: {
    changeCityName (state, cityName) //state 把公共状态传进来修改 {
      state.cityName = cityName
    },
    changeCityId (state, cityId) {
      state.cityId = cityId
    },
}

效果是一样的

不过当我们做搜索组件的时候,还需要请求后端拿取城市的影院信息,这样每次都要多次请求数据,能不能把数据缓存下来,减少对服务器的请求呢?有,就是使用vuex的异步缓存
注意mucation不支持异步,只支持同步
所以使用vuex的actions,可以支持异步和同步任务
vuex的异步:后端数据的缓存快照,减少重复数据请求,减少服务器的要,提高用户体验
vuex官网的工作流图:

所以我们在cinema页面就不直接请求数据,而是改用dispatch分发到actions中,不过注意当没有数据的时候,才会请求,有有数据时候,代表有缓存,不需要请求数据所以要加一个if条件

if (this.cinemaList.length === 0) {  this.$store.dispatch('getCinemaData', this.$store.state.cityId)
    }

actions中进行请求

actions: {
    getCinemaData (store, cityId) {
      http({
        url: `/gateway?cityId=${cityId}&ticketFlag=1&k=9755784`,
        headers: {
          'X-Host': 'mall.film-ticket.cinema.list'
        }
      }).then((res) => {
store.commit('changeCinemaData', res.data.data.cinemas)
      })
    }
  },

接着commit提交到mucation中对数据的缓存

mutations: {
 changeCinemaData (state, data) {
      state.cinemaList = data
    },
}

不过写完之后会有一个bug,就是切换城市时候,影院信息没有随之更新

分析一波原因发现,当cinemaList有缓存数据,就不请求后端更新数据了

所以需要在每次点击city组件时候,把cinemalist缓存直接清除,这样就会重新请求一次后端数据进行更新

methods: {
    handleLeft () {
      this.$router.push('/city')
      // 切换城市 直接把之前缓存的清空 cinemalist
  this.$store.commit('clearCinmaData')
    },

mucation *** 作

mucation{
clearCinmaData (state) {
      state.cinemaList = []
    },
 }

这样就可以成功切换城市信息了

search组件
点击搜索按,进入

 handleRight () {
      this.$router.push('/cinemas/search')
    }

使用vant组件库


搜索项的内容,注意设置v-if=value,当没有输入值时候,数据不创建出来


        
          
            {{ data.name }}
            {{ data.address }}
          
          
            ¥{{ data.lowPrice / 100 }}起
          
        

computedList方法,就是把包含输入的值的内容全部筛选出来
所以把影院列表中的name那一项的名字,凡是包含value的取出来,但是有大小写的区别,所以直接把输入的value值和获取得得到的值,全部都改成大写 方便匹配。下面地名也是如此 *** 作

 computedList () {
      return this.$store.state.cinemaList.filter(item => item.name.toUpperCase().includes(this.value.toUpperCase()) || item.address.toUpperCase().includes(this.value.toUpperCase()))
    }

效果:

然后把点击取消按钮,就返回cinema页面

onCancel () {
      this.$router.back()
    }

使用mixin混入,给某些组件混入一些功能,例如底部选显卡,在切换城市和搜索时不需要出现,就要隐藏

const obj = {
  created () {
    this.$store.commit('hide')
  },
  destroyed () {
    this.$store.commit('show')
  }
}
export default obj
 mixins: [obj],

没有底部按钮

不过要注意,当切换城市成功之后,再次刷新页面,会变成初始值,所以这就需要vuex的持久化-todo
使用 npm i--save vuex-persistedstate 下载vuex的持久化插件
引入插件

import createPersistedState from 'vuex-persistedstate'

在vuex.store下输入

// 每次数据引入都会在localstoage 缓存数据 达到一个持久化的效果
 plugins: [createPersistedState({
    reducer: (state) => {
      // 规定存储那个值
      return {
        cityId: state.cityId,
        cityName: state.cityName
      }
    }
  })],

原理:在locationStorage 里面保存着这些信息
这样的话就支持vuex中cityid和cityName数据持久化,刷新也不会丢失数据。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1321915.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-06-12
下一篇2022-06-12

发表评论

登录后才能评论

评论列表(0条)

    保存