### City页面数据渲染-难点:cityList数据结构分析
①api/index.js中添加city接口:
cityList:{ city:"/v3/city" }
②api下新建request.js:
import http from "@utils/request.js"; import api from "./index.js"; export const cityApi=()=>{ return http({ method:"get", url:api.cityList.city }) }
③将数据存在vuex中,所以在store/index.js中
引入cityApi:
import { cityApi } from "@api/request.js";
state中定义:
state: {
hotCity: [],
cityList: []
}
通过actions请求数据:
actions: { async getCityList({ commit }) { let data = await cityApi(); commit("mutationsCityList", data.data.cities); } }
actions中触发mutations中mutationsCityList()方法:
mutations: { mutationsCityList(state, cities) { let hotCity = [], cityList = []; // 热门城市 for (let i = 0; i < cities.length; i++) { if (cities[i].isHot == 1) { hotCity.push({ id: cities[i].id, nm: cities[i].nm }); } } // 城市列表 /* 分析数据结构: [ {title:A,list:[{id:1,nm:"鞍山"},{id:2,nm:"安庆"}]}, {title:B,list:[{id:1,nm:"北京"},{id:2,nm:"亳州"}]}, ] */ for (let i = 0; i < cities.length; i++) { // initials-首字母 let initials = cities[i].py.slice(0, 1).toLocaleUpperCase(); if (isFirst(initials).flag) {// 如果首字母在cityList中存在,则往这个字母对应的title下的list中push一个对象:{id,nm} cityList[isFirst(initials).index].list.push({ id: cities[i].id, nm: cities[i].nm }); } else {// 如果首字母在cityList中不存在,则将这个首字母作为title的值,push一个对象{title:A,list:[{id,nm}]} cityList.push({ title: initials, list: [{ id: cities[i].id, nm: cities[i].nm }] }); } } function isFirst(initials) { let index = -1, flag = false; for (let i = 0; i < cityList.length; i++) { if (cityList[i].title == initials) { flag = true; index = i; break; } } return { index, flag }; } cityList.sort(function (a, b) { return a.title.charCodeAt() - b.title.charCodeAt(); }); // 更改state中的hotCity、cityList state.hotCity = hotCity; state.cityList = cityList; // 存储到localStorage中 localStorage.setItem("hotCity",JSON.stringify(state.hotCity)); localStorage.setItem("cityList",JSON.stringify(state.cityList)); } }
③City页面created中执行请求函数,通过辅助函数拿到数据:
import {mapActions,mapState} from "vuex"; export default { name:"City", created() { this.getCityList(); }, computed: { ...mapState({ hotCity:state=>state.hotCity, cityList:state=>state.cityList }) }, methods: { ...mapActions({ getCityList:"getCityList" }) }, }
### 数据优化-存储到localStorage中
①设置好state.hotCity、state.cityList将其存储到localStorage中(store/index.js):
localStorage.setItem("hotCity",JSON.stringify(state.hotCity));
localStorage.setItem("cityList",JSON.stringify(state.cityList));
②在state中设置cityList和hotCity的时候,先判断localStorage中有没有(store/index.js):
state: { cityList:localStorage.getItem("cityList")?JSON.parse(localStorage.getItem("cityList")):[], hotCity:localStorage.getItem("cityList")?JSON.parse(localStorage.getItem("hotCity")):[] }
③在City/index.vue中进行数据请求时先判断localStorage中有没有cityList或hotCity:
created() { if(!localStorage.getItem("cityList")||!localStorage.getItem("hotCity")){ this.getCityList(); } }
### 点击右侧边栏字母去到对应的title
①为每个li绑定tap事件(scrollToTitle(index)),将下标值传过去:
<v-touch tag="li" v-for="(item,index) in cityList" :key="index" @tap="scrollToTitle(index)">{{item.title}}</v-touch>
②最外层盒子设置ref属性:
<div class="city_body" ref="city_body">
③在所有的字母标题(.city_title_letter)中找到当前下标对应的字母标题,将offsetTop设置给city_body的scrollTop:
methods: { scrollToTitle(index){ let title=this.$refs.city_list.querySelectorAll(".city_title_letter")[index]; let t=title.offsetTop; this.$refs.city_body.scrollTop=t; } }
### better-scroll的使用
①安装:npm i better-scroll
②引入:import BS from "better-scroll";
③mounted中实例化:this.BS=new BS(this.$refs.city_body);
注意:
1、city_body这个盒子里面只能有一个盒子,并且city_body的高度小于它的子级盒子高度。
2、如果使用了better-scroll,后代元素中的fixed定位都会失效。
3、better-scroll会破坏点击事件,所以在实例的时候,配置项中加上 click:true,tap:true
### better-scroll的scrollTo()方法
在使用better-scroll初始化city_body后,点击字母去到对应的字母title时,滚动有bug,通过scrollTo()解决。
在scrollToTitle()函数中用 this.BS.scrollTo(0,-t,500); 替代原来的 this.$refs.city_body.scrollTop=t; :
scrollToTitle(index){ let title=this.$refs.city_list.querySelectorAll(".city_title_letter")[index]; let t=title.offsetTop; // this.$refs.city_body.scrollTop=t; // 往下滚动是正值,往上滚动是负值,和数学里的y轴坐标相反 this.BS.scrollTo(0,-t,500); }
### better-scroll二次封装(父组件访问子组件内部的方法:this.$refs.ref属性值.方法)
①plugins下新建BetterScroll/index.vue:
<template> <div class="wrapper" ref="wrapper"> <slot></slot> </div> </template> <script> import BScroll from "better-scroll"; export default { name:"BScroll", mounted() { this.BScroll=new BScroll(this.$refs.wrapper,{click:true,tap:true}); }, methods: { scrollTo(l=0,t=0){ this.BScroll.scrollTo(l,t,500); } }, } </script> <style> .wrapper{ height: 100%; } </style>
②src下新建common/components.js(用于注册所有的组件):
import Vue from "vue";
import BScroll from "@plugins/BetterScroll/index.vue";
Vue.component(BScroll.name,BScroll);
③main.js中全局注册:
import "./common/components.js";
④City页面实现边缘回弹效果:
只需要用<BScroll><BScroll>标签将需要页面包裹,不需要引入better-scroll:
<template> <div class="city_body" ref="city_body"> <BScroll ref="BScroll"> <div> <!-- 热门城市 --> <div class="hot_city"> <div class="hot_title">热门城市</div> <div class="hot_city_list"> <div class="hot_city_name" v-for="item in hotCity" :key="item.id">{{item.nm}}</div> </div> </div> <!-- 城市列表 --> <div class="city_list" ref="city_list"> <div class="city_list_item" v-for="item in cityList" :key="item.index"> <div class="city_title_letter">{{item.title}}</div> <div class="city_list_name"> <div class="city_list_name_item" v-for="cityName in item.list" :key="cityName.id">{{cityName.nm}}</div> </div> </div> </div> </div> </BScroll> <!-- 城市列表下标 --> <aside class="city_list_title"> <ul> <v-touch tag="li" v-for="(item,index) in cityList" :key="index" @tap="scrollToTitle(index)">{{item.title}}</v-touch> </ul> </aside> </div> </template>
⑤City页面实现点右击侧边栏滚动到对应title:
(1)在<BScroll></BScroll>标签上添加ref属性:
<BScroll ref="BScroll">
(2)scrollToTitle()方法中使用 this.$refs.BScroll.scrollTo(0,-t); :
scrollToTitle(index){ let title=this.$refs.city_list.querySelectorAll(".city_title_letter")[index]; let t=title.offsetTop; // this.$refs.city_body.scrollTop=t; // 往下滚动是正值,往上滚动是负值,和数学里的y轴坐标相反 // this.BS.scrollTo(0,-t,500); this.$refs.BScroll.scrollTo(0,-t); }
### better-scroll组件(BScroll)横向滚动
①better-scroll配置项中设置scrollX:true,支持横向滚动:
this.BScroll=new BScroll(this.$refs.wrapper,{ click:true, tap:truescrollX: true });
②components/RecommendList/index.vue中用<BScroll></BScroll>标签将原来的内容包裹:
<template> <BScroll> <div class="recommend_list"> <div class="recommend_list-item" v-for="item in recommend" :key="item.target_id" :data-id="item.target_id" > <div> <img v-lazy="item.image" /> </div> <div class="goodsName">{{item.title}}</div> <div class="price"> <span>¥{{item.vip_price}}/{{item.volume}}</span> <span>+</span> </div> </div> </div> </BScroll> </template>
### sass
①安装:npm i sass-loader node-sass -D
②style标签添加lang属性:
<style lang="scss"> $color:red; .classify{ background: $color; } </style>
注意:
1、sass定义变量用$,less定义变量用@
2、sass基于ruby,less基于JavaScript
3、less比sass简单,less中可以直接写css语句
4、sass通过服务端处理,less是通过客户端处理,sass的解析速度更快