赞
踩
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 //必需要写
}
}
}
})
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"></i>
<span>电影</span>
</router-link>
<router-link to="/cinema" active-class="on" tag="li">
<i class="iconfont"></i>
<span>影院</span>
</router-link>
<router-link to="/mine" active-class="on" tag="li">
<i class="iconfont"></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>
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('')
})
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.详情页
接口准备:
<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 ? '' : ''}}</span></div>
</div>
这里需要用到过滤器过滤事件戳 用到了moment包
import Vue from "vue"
import moment from "moment"
Vue.filter('dataFilter',date=>{
return moment(date).format('YYYY-MM-DD')
})
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; }, });
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
具体使用见上图
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]” 是需要传一个数组的 不然显示不了,如果传入多个值 可以左右滑动
按照官网文档引入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)
},
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()
}
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')
}
}
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来获取数据或者调用方法,比如这样
网址: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: {...} })
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。