Vue模仿卖座项目

01 搭建项目和配置反向代理

1.安装node
2.初始化vue项目 vue create 项目名称
3.将package.json中的"scripts"下的serve 改为start 方便我们运行项目

"scripts": {
    "start": "vue-cli-service serve",//serve 改为start 
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },

4.配置反向代理 在vue.config.js文件里面

module.exports = defineConfig({
  transpileDependencies: true,
  devServer:{
    proxy:{//配置反向代理
      "/ajax":{ //前端所有以/ajax开头的请求连接下面的域名,并用该服务器发送请求
        target:'https://i.maoyan.com',
        changeOrigin : true //必需要写   
      }
    }
  }
})

02项目的准备工作

1.配置好卖座电影的请求接口,拿到卖座的信息

axios({
      url:'https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=440019',
      headers:{
        'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16499297417748121001985"}',
        'X-Host': 'mall.film-ticket.film.list'
      }
    }).then((res)=>{
      console.log(res);
    })

2.创建TableBar.vue组件 此组件是下面的table栏目 主要是路由链接

<router-link to="/film" active-class="on" tag="li">
                <i class="iconfont">&#xe625;</i>
                <span>电影</span>
            </router-link>
            <router-link to="/cinema" active-class="on" tag="li">
                <i class="iconfont">&#xe623;</i>
                <span>影院</span>
            </router-link>
            <router-link to="/mine" active-class="on" tag="li">
                <i class="iconfont">&#xe628;</i>
                <span>我的</span>
            </router-link>

3.配置router文件

import Vue from 'vue'
import VueRouter from 'vue-router'


Vue.use(VueRouter)

const routes = [
  {
    path: '/film',
    component: () => import('../views/film')
  },
  {
    path: '*',
    redirect: "/film"//重定向设置,当所有路由都不匹配时,走film路由
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

4.写路由页面film

<template>
  <div class="wrap">
    <div class="lunbo" style="background: green; height: 150px">轮播</div>
  </div>
</template>

5.在APP.VUE中引入TableBar并且注册

import tablebar from "./components/TableBar";
export default {
  components: {
    tablebar,
  },
};

6.main引入路由并且注册

import router from './router'


new Vue({
  router,
  // store,
  render: h => h(App)
}).$mount('#app')

7.项目跑起来的时候经常会报错,比如Too many blank lines at the end of file. Max of 0 allowed. (no-multiple-empty-lines)
,解决办法
关闭eslint校验工具(不关闭会有各种规范,不按照规范就会报错)
根目录下创建vue.config.js,进行配置

module.exports = {
  //关闭eslint
  lintOnSave: false
  }

8.将tab切换栏的图标弄好
从阿里图标库选择好图标下载至本地
解压完成后将文件夹复制到public文件夹下面
再设置public/index

    <link rel="stylesheet" href="<%= BASE_URL %>font/iconfont.css">

去TableBar组件写图标

 <i class="iconfont icon-dianying"></i>

03Film组件

1.views目录下创建Film组件
2.配制路由,通过路由方式显示Film组件在App.vue中

const routes = [
  {
    path: '/film',
    component: () => import('../views/film'),

3.views目录下创建film目录,film目录用于保存Film.vue组件的子组件,并且film目录下创建FilmHead.vue组件
4.在Film.vue组件中加载FilmHeader.vue组件

<template>
  <div class="wrap">
    <div class="lunbo" style="background: green; height: 150px">轮播</div>
    <filmHeader></filmHeader>
    <!-- 对应路由显示内容 -->
    <router-view></router-view>
  </div>
</template>
import filmHeader from './film/FilmHead.vue'
export default{
  components:{
    filmHeader
  }
}

5.FilmHead.vue组件中添加路由导航

<template>
    <div>
        <ul>
            <!-- 添加路由导航 -->
            <router-link to="/film/HotPlaying" active-class="active" tag="li">正在热搜</router-link>
            <router-link to="/film/WillPlaying" active-class="active" tag="li">即将上映</router-link>
        </ul>
    </div>
</template>

6.film目录下创建HotPlaying.vue和WillPlaying.vue组件
7.配制FilmHead.vue组件中路由导航与HotPlaying.vue,WillPlaying.vue的跳转路由

const routes = [
  {
    path: '/film',
    component: () => import('../views/film'),
    children: [{
      path: "HotPlaying",
      component: () => import('../views/film/HotPlaying')
    },
    {
      path: "WillPlaying",
      component: () => import("../views/film/WillPlaying"),//路由懒加载方式处理
    },
    {
      path: "",
      redirect:"HotPlaying"
    },
    ]
  },

8.HotPlaying.vue组件中请求数据

 axios({
      url: "https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=5036565",
      headers: {
        "X-Client-Info":
          '{"a":"3000","ch":"1002","v":"5.2.0","e":"16499297417748121001985"}',
        "X-Host": "mall.film-ticket.film.list",
      },
    }).then((res) => {
      this.dataList = res.data.data.films;
      console.log(this.dataList);
    });

9.渲染页面+演职人员过滤+修改过滤器

 <li v-for="film in dataList" :key="film.filmId">
        <img :src="film.poster" alt=""/>
        <h3>{{film.name}}</h3>
        <!-- 要对所有的演员过滤输出 -->
        <p>主演:{{film.actors | actorsfilter}}</p>
      </li>
import Vue from 'vue'
// 定义过滤
Vue.filter('actorsfilter',actors=>{
    //当actors不存在时,程序报错
    if(!actors) return "暂无主演"
    //将所有的演员过滤出来,以字符串的方式返回
    return actors.map(item=>item.name).join('')
})

04轮播图和详情页

1.将Film.vue组件显示的轮播位置修改为一个轮播组件
安装轮播图 npm install swiper

 "swiper": "^8.1.1",

2.film目录下定义FilmSwiper.vue组件,并实现轮播

<template>
  <div class="swiper-container">
    <div class="swiper-wrapper">
      <!-- 如果需要分页器 -->
      <div class="swiper-pagination"></div>
    </div>
  </div>
</template>
import Swiper from "swiper/swiper-bundle.js"; //引入js
import "swiper/swiper-bundle.min.css"; //引入css
export default {
  mounted() {
    new Swiper(".swiper-container");
  },
};
</script>
<style scoped>
    .swiper-container{
        overflow: hidden;
    }
</style>

3.完善轮播:

<!-- 如果需要分页器 -->
      <div class="swiper-pagination"></div>
 mounted() {
    new Swiper(".swiper-container", {
      loop: true,
      // 如果需要分页器
      pagination: {
        el: ".swiper-pagination",
      },
    });
  },

4.Film.vue:添加轮播图片

 data() {
    return {
      imgSrc: [
        "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2Faeeb41d1d86fd8d438777854c7c8816def0d47db54c53f-80PyKT_fw658&refer=http%3A%2F%2Fhbimg.b0.upaiyun.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652684582&t=da2facbc8e5089c95b1aa80966b94e49",
        "https://img1.baidu.com/it/u=3107583039,2804965166&fm=253&fmt=auto&app=138&f=JPEG?w=720&h=405",
        "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F811%2F041515101355%2F150415101355-11-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652684602&t=f5dd46ab2d16bd3f007361f6dc494b20",
      ],
    };

图片在网上随便找的

 <filmSwiper>
      <div class="swiper-slide" v-for="(src, index) in imgSrc" :key="index">
        <img :src="src" style="height:200px;width:100%" alt="" />
      </div>
    </filmSwiper>

5.详情页
接口准备:

  • https://m.maizuo.com/gateway?filmId=5610&k=9403003
    点击每一个film列表,跳转到详情页,显示对应的详情信息.在film文件夹中创建Detil路由页面
    在hotplaying页面中点击就跳转详情页面 点击的时候需要带id给路由
 <li v-for="film in dataList" :key="film.filmId" @click="toDail(film.filmId)">

路由配置

const routes = [
  {
    path: '/film',
    component: () => import('../views/film'),
    children: [{
      path: "HotPlaying",
      component: () => import('../views/film/HotPlaying')
    },
    {
      path: "WillPlaying",
      component: () => import("../views/film/WillPlaying"),//路由懒加载方式处理
    },
    {
      path: "",
      redirect:"HotPlaying"
    },
    ]
  },
  {
    path: '*',
    redirect: "/film"//重定向设置,当所有路由都不匹配时,走film路由
  },
  {
    path:'/detail',
    component:()=> import('../views/film/Detail')
  }
]

6.将axios请求提取为模块并发送请求
src目录下新建util目录,util目录下新建http.js模块

import axios from 'axios'
const http = axios.create({
    baseURL:"https://m.maizuo.com",
    timeout : 10000,
    headers : {
        'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.0.4","e":"16109427851267187151011841","bc":"440100"}'
    }
})
export default http

去detail发送请求
这次用async await接收

 async mounted() {
    try {
      const re = await http({
        url: `/gateway?filmId=${this.$route.query.filmId}&k=580815`,
        headers: {
          "X-Host": "mall.film-ticket.film.info",
        },
      });
      console.log(re);
      this.filmInfo = re.data.data.film;
    } catch (error) {
      console.log("请求出错",error);
    }
  },

7.Detail.vue组件展示数据
数据保存data

 data(){
        return{
            filmInfo : null,  
            isShow:false,
        }
    },

渲染页面

<div v-if="filmInfo">
        <div :style="{backgroundImage:'url('+filmInfo.poster+')'}" style="height:200px;background-size:cover;background-position:center;"></div>
        <h3>{{filmInfo.name}}--{{filmInfo.filmType.name}}</h3>
        <p>{{filmInfo.category}}</p>
        <!-- 使用过滤器过滤时间戳 -->
        <p>{{filmInfo.premiereAt | dataFilter}} 上映</p>
        <p>{{filmInfo.nation}}|{{filmInfo.runtime}}分钟</p>
        <!-- isShow为true显示所有内容,为false只显示一部分 -->
        <div :class="isShow ? '' : 'synopsis'" class='profiles'>
            {{filmInfo.synopsis}}
        </div>
        <!-- 上下方向控制 -->
        <div style="text-align:center"><span class="icon iconfont" @click="isShow = !isShow">{{isShow ? '&#xe601;' : '&#xe600;'}}</span></div>
    </div>

这里需要用到过滤器过滤事件戳 用到了moment包

import Vue from "vue"
import moment from "moment"
Vue.filter('dataFilter',date=>{
    return moment(date).format('YYYY-MM-DD')
})

05详情页的完善

1.Detail.vue组件图片轮播
有两个轮播图,分为演职人员轮播和剧照,直接在src\views\detail\DetailSwiper.vue

<template>
    <div class="swiper-container" :class="swiperClass">
        <div class="swiper-wrapper">
            <slot></slot>
        </div>
    </div>
</template>
import Swiper from "swiper/swiper-bundle.js"; //引入js
import "swiper/swiper-bundle.min.css"; //引入css
export default {
    props:{
        perslide:{
            type:Number,
            default:1
        },
        swiperClass:{
            type:String,
            default:'actors-class',
        }
    },
   mounted() {
    new Swiper("."+this.swiperClass, {
      slidesPerView: this.perslide, //一次性显示slide的个数
      spaceBetween: 10, //每一个slide间隔的宽度
      freeMode: true,
    });
  }
 }
<style lang="scss" scoped>
    .swiper-wrapper{
        img{
            width:100%;
        }
    }
    .swiper-container{
        overflow: hidden;
    }

这里:class="swiperClass"必须是动态的 不然多次使用同一个detail-swiper组件,在组件内部实例化Swiper的是同一个配制,会导致Swiper混乱。

再去src\views\film\Detail.vue引入轮播图

import detailSwiper from "../detail/DetailSwiper.vue";
  components: {
    detailSwiper,
  },
<detailSwiper :perslide="4" swiperClass="actors-class">
      <div
        class="swiper-slide"
        v-for="(actor, index) in filmInfo.actors"
        :key="index"
      >
        <img :src="actor.avatarAddress" alt="" />
        <div style="text-align: center; font-size: 12px">
          <div>{{ actor.name }}</div>
          <div>{{ actor.role }}</div>
        </div>
      </div>
    </detailSwiper>
    <h3>剧照</h3>
    <detailSwiper :perslide="2" swiperClass="photos-class">
      <div
        class="swiper-slide"
        v-for="(photo, index) in filmInfo.photos"
        :key="index"
      >
        <div
          :style="{ backgroundImage: 'url(' + photo + ')' }"
          style="
            height: 100px;
            background-size: cover;
            background-position: center;
          "
        ></div>
      </div>
    </detailSwiper>

这里需要用:perslide=“4” swiperClass="actors-class"通过props传给siwper组件,让swiper-container加上一个动态class。

2.detail目录下创建悬停组件DetailHeader.vue
创建src\views\detail\DetailHead.vue

<template>
    <div>{{title}}</div>
</template>
<script>
export default {
    props:['title']
}
</script>
<style lang="scss" scoped>
    div{
        width: 100%;
        height: 50px;
        line-height: 50px;
        text-align: center;
        position: fixed;
        top: 0;
        left: 0;
        z-index: 99;
        background-color: white;
    }
</style>

在src\views\film\Detail.vue引入DetailHead.vue

import detailHead from "../detail/DetailHead.vue";

注册

components: {
    detailSwiper,
    detailHead,
  },

调用,通过props传值

<detail-head :title="filmInfo.name"   v-top></detail-head>

因为有特殊需求 所以需要自定义指令

Vue.directive("top", {
  inserted(el) {  //指使用v-top的元素挂载到页面自动触发
    el.style.display = "none";	
    window.onscroll = () => {
      if (
        (document.documentElement.scrollTop || document.body.scrollTop) > 50
      ) {
        el.style.display = "block";
      } else {
        el.style.display = "none";
      }
    };
  },
  unbind() {
    //组件销毁时,解绑onscroll
    window.onscroll = null;
  },
});

06.影院页面

1.views目录下创建Cinema.vue组件

<template>
  <div class="cinema" :style="{ height: height }">
    <ul>
      <li v-for="cinema in cinemaList" :key="cinema.cinemaId">
        <div>{{ cinema.name }}</div>
        <div class="address">{{ cinema.address }}</div>
      </li>
    </ul>
  </div>
</template>
<script>
import http from "@/untl/http.js";
import BetterScroll from "better-scroll";
export default {
  data() {
    return {
      cinemaList: [],
      height: 0,
    };
  },
  created() {
    this.height = document.documentElement.clientHeight - 50 + "px";
    http({
      url: "/gateway?cityId=440300&ticketFlag=1&k=8935392",
      headers: {
        "X-Host": "mall.film-ticket.cinema.list",
      },
    }).then((res) => {
      console.log(res);
      this.cinemaList = res.data.data.cinemas;
      this.$nextTick(() => {
        new BetterScroll(".cinema", {
          scrollbar: {
            fade: true,
          },
        });
      });
    });
  },
};
</script>
<style lang="scss" scoped>
li {
  padding: 5px;
  .address {
    font-size: 12px;
    color: gray;
  }
}
.cinema {
  overflow: hidden; //溢出隐藏才能全better-scroll起作用
  position: relative; //让滚动条相对于cinema定位
}
</style>

2.配置路由

 {
      path:"/cinema",
      component: () => import("../views/Cinema")
  },

3.发数据 渲染页面
见上图

4.这里渲染好了感觉页面不够平滑,安装betterscrol,使用户体验更好
npm install --save better-scroll
具体使用见上图

06组件库的使用

1.安装组件库
cnpm i -S element-ui 饿了么ui组件库
npm i -S vant vant组件库
2.配置按需引入组件库
对于使用 babel7 的用户,可以在 babel.config.js 中配置

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins: [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ],
    ['import', {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    }, 'vant']
  ],

}

上面是用了element ui 也用了vant

3.在不使用插件的情况下,可以手动引入需要的组件。

import { InfiniteScroll,Loading,Message,Image} from "element-ui";
import { Toast } from 'vant';

Vue.use(InfiniteScroll);
Vue.use(Loading); 
Vue.use(Image);
Vue.prototype.$message = Message;
Vue.use(Toast);

我是直接在main.js中引入的 这样在所有页面都可以直接使用

4.页面懒加载
这里使用的是element ui的InfiniteScroll处理热门页面的电影列表
使用Loading处理页面请求未回应的情况

 <div class="hotPlay" style="overflow: auto; height: 600px">
    <ul
      v-loading="loading"
      v-infinite-scroll="load"
      infinite-scroll-disabled="disabled"
      infinite-scroll-distance="10"
      infinite-scroll-immediate="false"
      infinite-scroll-delay ='0'
    >
    。。。。。.。。...。。
load() {
      console.log(this.temp);
      if (this.dataList.length == 23) {
        //没有数据了
        console.log("0没有数据了");
        this.temp = !this.temp;
        if (this.temp) {
          this.$message({
            message: "没有数据了",
            type: "warning",
          });
        }
        return;
      }
      this.loading = true;
      this.current++;
      console.log(this.current);
      http({
        //根据页码请求数据
        url:
          "/gateway?cityId=440100&pageNum=" +
          this.current +
          "&pageSize=10&type=1&k=792875",
        headers: {
          "X-Host": "mall.film-ticket.film.list",
        },
      }).then((res) => {
        //console.log(res.data.data.films)
        //当请求没有数据时res.data.data.films返回空数组,会出现无限请求的bug
        this.dataList = [...this.dataList, ...res.data.data.films];
        this.loading = false;
      });
    },
data() {
    return {
      dataList: [],
      current: 1, //加载数据的页数
      total: 0, //数据总长度
      loading: false,
      temp: false,
    };
  },

这里的大盒子div一定要设置一个高 不然ui组件无法正确调用load方法

infinite-scroll-immediate=“false” 要设置fasle 阻止默认就发送一次

v-loading直接用属性的方式加到url 用loading: false,来控制是否显示

这里还用了一个Message

让页面加载到最底部弹出提示框

if (this.dataList.length == 23) {
        //没有数据了
        console.log("0没有数据了");
        this.temp = !this.temp;
        if (this.temp) { //处理往上拉还调用load的bug
          this.$message({
            message: "没有数据了",
            type: "warning",
          });

接口返回的数据和total对不上 所以我把写死了

5 Toast模块,加载提示,封装到axios里面 ,有时网络较慢,数据正在加载,设置一个加载提示的效果,用户体验会更好,这里使用到vant中的Toast组件来实现
1.src\untl\http.js文件下面
使用axios中的拦截器功能,在请求前实现加载提示,请求成功后关闭提示功能即可。

const http = axios.create({
    baseURL:"https://m.maizuo.com",
    timeout : 10000,
    headers : {
        'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.0.4","e":"16109427851267187151011841","bc":"440100"}'
    }
})
http.interceptors.request.use(function (config){
    Toast.loading({
        message: '加载中...',//提示信息
        forbidClick: true,//允许背景点击
        overlay: true,//显示遮罩
        loadingType: "spinner", //加载图标类型,默认circular
        duration: 0 //展示时长(ms),值为 0 时,toast 不会消失
    });
    return config;
},function(error){
    return Promise.reject(error);
}
)
http.interceptors.response.use(function (response) {
    //请求响应后关闭提示
    Toast.clear()
    return response;
  }, function (error) {
    return Promise.reject(error);
});

7.利用element ui中的image实现查看大图功能

Vue.use(Image);

改装详情页里面的剧照 让其变成点击可以大图 之前使用背景图片 为了更好的跟el-image配合 我改成了元素显示

<el-image
          style="height: 100px; width: 180px"
          :src="photo"
          :preview-src-list="[photo]"
        ></el-image>

设置好图片的宽高
:preview-src-list=“[photo]” 是需要传一个数组的 不然显示不了,如果传入多个值 可以左右滑动

07NavBar导航组件和城市列表的实现

按照官网文档引入navbar 再注册
在src\views\Cinema.vue路径引入

<van-nav-bar title="影院" @click-left="onClickLeft">
      <template #left >
        上海<van-icon name="arrow-down" color="black"/>
      </template>
      <template #right>
        <van-icon name="search" size="18" color="black" />
      </template>
    </van-nav-bar>
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
 onClickLeft() {
      this.$router.push('/city')
    },
    onClickRight() {
       console.log("按钮");

点击头部导航栏右边跳转到city页面

在这里插入图片描述
创建city页面配置好路由
发请求捞取数据
接口:https://m.maizuo.com/gateway?k=1719861
在这里插入图片描述
把响应回来的数据做处理

 mounted() {
    http({
      url: "/gateway?k=2497277",
      headers: {
        "X-Host": "mall.film-ticket.city.list",
      },
    }).then((res) => {
      console.log(res),
        (this.citiesList = this.formatData(res.data.data.cities));
      console.log(this.citiesList);
    });
  },
formatData(cities) {
      let letterList = [];
      for (let code = 65; code < 91; code++) {
        letterList.push(String.fromCharCode(code));
      }
      let newCitiesList = [];
      letterList.forEach((letter) => {
        let list = cities.filter((city) => {
          return city.pinyin[0].toUpperCase() === letter;
        });
        if (list.length > 0) {
          newCitiesList.push({
            type: letter,
            list: list,
          });
        }
      });
      return newCitiesList;

使用vant中的IndexBar 索引栏组件将数据显示在页面
引入

import Vue from 'vue';
import { IndexBar, IndexAnchor,Cell } from 'vant';

Vue.use(IndexBar);
Vue.use(IndexAnchor);
Vue.use(Cell);

把数据结合indexBar渲染到页面

<template>
    <div>
        <!-- 通过 index-list 属性自定义展示的索引字符列表。 -->
        <van-index-bar :index-list="computeCitiesList">
            <div v-for="cities in citiesList" :key="cities.type">
                <van-index-anchor style="background:#ccc;" :index="cities.type" />
                <van-cell :title="city.name" v-for="city in cities.list" :key="city.cityId"/>
            </div>
        </van-index-bar>
    </div>
</template>

在这里插入图片描述
:index-list=“computeCitiesList” 需要绑定这个 不然点击右侧的跳转会乱

 computed:{
        //通过计算属性处理索引列表
        computeCitiesList(){
            return this.citiesList.map(item => item.type)
        }
    },

再利用Toast点击字母索引有显示

 <van-index-bar :index-list="computeCitiesList" class="indexbar" @select="handleSelect">

会自动传入index

  handleSelect(index){
      Toast(index)
    },

在这里插入图片描述

08VUEX的引入

vuex是一个专门为vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
store/index.js文件中

import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//vue中注册Vuex
Vue.use(Vuex)
//Vuex.Store()用于配制存储状态
export default new Vuex.Store({
  state: {//状态管理属性
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

设置城市列表的管理初始状态

  state: {
    cityId: '310100',
    cityName: '上海'
  },

main.js中引入store/index.js模块,全局作用于项目中

import router from './router'
Vue.use(store)
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

City.vue组件中通过store记录当前点击的城市状态

handleClick(cityId,cityName){
      this.$store.commit('clearList')
      this.$store.commit('undateCityId',cityId)
      this.$store.commit('undateCityName',cityName)
      this.$router.back()
    },

src\store\index.js里面

 state: {
    cityId: '340800',
    cityName: "安庆",
   },
 mutations: {
    undateCityId(state, cityId) {
      state.cityId = cityId
    },
    undateCityName(state, cityName) {
      state.cityName = cityName
    },

Cinema.vue实时获取City中记录的城市状态
template中直接访问:$store.state.cityName

<template #left>
        {{ $store.state.cityName }}<van-icon name="arrow-down" color="black" />
</template>

跳转到详情页面时,tabBar隐藏,退出(销毁)详情页时显示tabBar
Detail.vue组件中

 mounted() {
    this.getfilmInfo(),
    this.$store.commit("hide");
  },
  beforeDestroy() {
    this.$store.commit("show");
  },

src\store\index.js中mutations加上方法

show(state) {
      state.tabBarState = true
    },
    hide(state) {
      state.tabBarState = false
    },

App.vue中修改tabBar

<template>
  <div>
    <!-- <tablebar></tablebar> -->
    <!-- 根据store中tabBar状态决定是否显示tabBar -->
    <tablebar v-show="$store.state.tabBarState"></tablebar>
    <!-- 对应路由的视图显示的插槽 -->
    ......
  </div>
</template>

VUEX异步发请求
有时候,我们在多个组件中都要请求同一个接口的所有数据,这种情况不应该每一个组件都来重复请求一次,这样就浪费了更多的服务器资源。使用store状态管理,可以保存一次请求状态,后面多次使用数据。这样既减轻了服务器压力,节约了服务器资源,又提升了组件加载速度,自然也提高了用户体验。

state: {
    cityId: '310100',
    cityName: '上海',
    tabBarState: true, //tabBar状态,默认显示
    dataList: [] //保存后端数据状态
  },

Cinema.vue组件中需要判断store中的dataList状态是否有数据,如果没有,需要按store的异步处理流程来实现数据的请求

mounted() {
    this.height = document.documentElement.clientHeight - 50 + "px";
    if (this.$store.state.dataList.length === 0) {
      this.$store
        .dispatch("getCinemaDate", this.$store.state.cityId)
        .then(() => {
          this.$nextTick(() => {
            new BetterScroll(".cinema", {
              scrollbar: {
                fade: true, //滚动时显示滚动条,不滚动时隐藏滚动条
              },
            });
          });
        });
    }else{
      this.$nextTick(() => {
            new BetterScroll(".cinema", {
              scrollbar: {
                fade: true, //滚动时显示滚动条,不滚动时隐藏滚动条
              },
            });
          });
    }
  },

store中

actions: {
    getCinemaDate(store, cityId) {
      return http({
        url: '/gateway?cityId=' + cityId + '&ticketFlag=1&k=610006',
        headers: {
          'X-Host': ' mall.film-ticket.cinema.list'
        }

      }).then(res => store.commit("setCinemaData", res.data.data.cinemas))

    }
  },
mutations: {
    undateCityId(state, cityId) {
      state.cityId = cityId
    },
    undateCityName(state, cityName) {
      state.cityName = cityName
    },
    show(state) {
      state.tabBarState = true
    },
    hide(state) {
      state.tabBarState = false
    },
    setCinemaData(state, data) {
      state.dataList = data
    },
    clearList(state){
      state.dataList = []
    }

  },

Cinema.vue组件中更新template

<template>
    <div>
        ......
        <div class="cinema" :style="{height:height}">
            <ul>
                <!-- <li v-for="cinema in cinemaList" :key="cinema.cinemaId"> -->
                <!-- 从store中获取dataList -->
                <li v-for="cinema in $store.state.dataList" :key="cinema.cinemaId">
                    <div>{{cinema.name}}</div>
                    <div class="address">{{cinema.address}}</div>
                </li>
            </ul>
        </div>
    </div>
</template>

这里又出现了新的bug,当我们切换城市的时候,程序走的是store中缓存的状态数据。不再会根据城市加载新的数据,如此又需要如何解决呢?
解决办法:当在city.vue城市列表组件中点击选中城市时,将store中的dataList置空即可
city.vue中

 handleClick(cityName,cityId){
            //清空store中的dataList城市列表数据,重新根据当前选中的城市信息加载新的数据
        this.$store.commit("clearDataList")
            
        //集中式管理状态
            this.$store.commit("updateCityName",cityName)
            this.$store.commit("updateCityId",cityId)
            this.$router.back()
        }

09search组件

  • 在Cinema.vue页面中,点击右边search按钮,跳转到显示搜索组件。

Cinema.vue组件中
在这里插入图片描述

<van-nav-bar
      title="影院"
      @click-left="onClickLeft"
      @click-right="onClickRight"
    >
 ............
  onClickRight() {
      this.$router.push("/cinema/search")
    },

配置路由

{
    path:"/cinema/search",
    component:()=> import("../views/Search")
  }

创建src\views\Search.vue
这里面需要获取dataList
Search组件中可以直接获取上一次store中请求的dataList数据,方便后续的搜索功能实现,但是这里出现了一个问题,当用户在这个页面直接重新刷新页面时,页面直接显示的是当前的Search组件页面,程序并没有走store中的数据请求,所以导致dataList为空了,所以要做如下处理

 if (this.$store.state.dataList.length === 0) {
      this.$store.dispatch("getCinemaDate", this.$store.state.cityId);
    }

再利用vant组件

<template>
    <div>
        <van-search
            v-model="value"
            show-action
            placeholder="请输入搜索关键词"
        />
    </div>
</template>
import Vue from 'vue';
import { Search } from 'vant';
Vue.use(Search);
export default {
    data(){
        return {
            value: ""
        }
    },
    mounted(){
       ......
    }
}
</script>

在这里插入图片描述
根据数据实现搜索功能,使用计算属性computed处理数据搜索过滤

 computed:{
      computedDataList(){
        //如果用户没有输入内容  就返回空数组
          if(this.value == ''){
              return []
          }
          return this.$store.state.dataList.filter(
              item =>{
                  return item.address.toUpperCase().includes(this.value.toUpperCase()) || item.name.includes(this.value.toUpperCase())
              }
          )
      }
  },
<van-search v-model="value" placeholder="请输入搜索关键词"  show-action @cancel='onCancel'/>
    <van-list>
      <van-cell v-for="item in computedDataList" :key="item.cinemaId">
          <div>{{item.name}}</div>
          <div class="address">{{item.address}}</div>
      </van-cell>
    </van-list>

在这里插入图片描述
点击“取消”按钮,返回cinema组件页面

 methods:{
      onCancel(){
          this.$router.replace('/cinema')
      }
  }

10 store模块化

1,应用层级的状态都应该集中在单个的store对象中

2,提交mutation是更改状态的唯一方法,并且这个过程是同步的

3,异步逻辑都应该封装到action里面

store模块化处理 从store/index.js中提取如下模块

store/index.js ==> 提取模块:

​ 1,cinemaModule.js

​ 2,cityModule.js

​ 3,tabBarModule.js

store下创建modules目录,modules目录下分别创建各自模块
在这里插入图片描述
cinemaModule.js

import http from "../../untl/http"
const module = {
  state: {
    dataList: []
  },
  mutations: {
    setCinemaData(state,data) {
      state.dataList = data
    },
    clearList(state){
      state.dataList = []
    }
  },
  actions: {
    getCinemaDate(store, cityId) {
      return http({
        url: '/gateway?cityId=' + cityId + '&ticketFlag=1&k=610006',
        headers: {
          'X-Host': ' mall.film-ticket.cinema.list'
        }

      }).then(res => store.commit("setCinemaData", res.data.data.cinemas))

    }
  },
  namespaced:true

}
export default module

另外两个都是一样,需要什么数据,什么方法就写什么方法

此时store/index.js中引入以上模块

import Vue from 'vue'
import Vuex from 'vuex'
import cinemaModule from './modules/cinemaModule'
import cityModule from './modules/cityModule'
import tabBarModule from './modules/tabBarModule'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {

  },
  mutations: {
  },
  actions: {
  },
  modules: {
    cinemaModule,
    cityModule,
    tabBarModule,
  }
})

这样看起来就简洁很多
模块提取到这,还没有完全能够使用,此时运行项目会报错,这个报错并没有很清楚的反映bug的具体信息,原因在于,每一个模块中的state状态属性,无法在组件中直接获取了,如this.$store.state不能获取,这里可以使用vuex中的mapState辅助函数来映射(获取)状态,用mapMutations来映射同步方法,用mpaActions来映射异步,获取方式:mapState(“模块名称”,[状态1,状态2,状态3…]),结果每回一个对应模块中状态组成的对象。mapState可以在使用这些状态的组件中的计算属性中使用。
1,各个模块下开启命名空间属性 namespaced:true

const module = {
    namespaced:true,//开启命名空间
    state: {},
    mutations: {
        ...
    },
    actions: {...}
}
export default module

回到页面
App.vue中需要使用tabBarModule.js中的tabBarState状态(显示tabBar)
引入vuex中的mapState函数,通过mapState()在计算属性中获取需要的状态属性

<template>
  <div>
    <!-- <router-view></router-view> -->
    <tablebar v-show="tabBarState"></tablebar>
    <!-- 对应路由的视图显示的插槽 -->
    <router-view></router-view>
  </div>
</template>
<script>
import {mapState,mapMutations,mapActions} from "vuex"
import tablebar from "./components/TableBar";
export default {
  components: {
    tablebar,
  },
  computed:{
    ...mapState('tabBarModule',['tabBarState'])
  }
};
</script>

其余的页面也是一样,都需要引入import {mapState,mapMutations,mapActions} from “vuex”,
…mapState(模块名称,[模块中的数据或者方法]),其中state都是放在computed里面,如果是
…mapMutations或者…mapActions是要放在methods里面的,如下图
在这里插入图片描述
当那我拿到映射的数据后,就可以简化代码了,不用再写$store了,我们直接可以用this来获取数据或者调用方法,比如这样
在这里插入图片描述

11vuex数据持久化插件:vuex-persistedstate

网址:https://github.com/robinvdvleuten/vuex-persistedstate
安装命令

cnpm install --save vuex-persistedstate
store/index.js中使用

import Vue from 'vue'
import Vuex from 'vuex'
//引入持久化模块
import createPersistedState from "vuex-persistedstate"

//引入模块
...
Vue.use(Vuex)

export default new Vuex.Store({
  //数据持久化
  plugins: [createPersistedState()],
  //公共状态
  state: {},
  //集中式修改状态
  mutations: { },
  //异步
  actions: {},
  //各个模块
  modules: {...}
})

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值