目录
Vue 实例实战之 Vue webpack 仿去哪儿网App页面开发(应用中的几个页面简单实现)
七、router路由管理, store vuex 状态管理 说明
十一、src/assets/styles 存放css 样式、常用的变量样式参数等
Vue 开发的一些知识整理,方便后期遇到类似的问题,能够及时查阅使用。
本节介绍,Vue 开发的实例实战,模仿开发去哪儿网的几个页面 ,体验 Vue 在实战中应用,欢迎指出,或者你有更好的方法,欢迎留言。
1、vue 2.5.2
2、vue-router 3.0.1
3、vuex 3.0.1





1、环境构建,并且 vue init webpack xxx_工程名,根据提示创建工程
具体环境搭建过程:Web 前端 之 Vue webpack 环境的搭建及工程创建简单整理_仙魁XAN的博客-CSDN博客

2、工程文件目录结构如下

3、安装依赖,如果下面的一些依赖没有安装,可以对应使用 npm install 插件名@版本号 --save 先安装插件包

4、src 开发文件结构说明

5、在 main.js 中引入 reset.css 用作重置浏览器标签的样式表,统一样式,border.css 移动端1像素边框,fastclick 解决移动端click事件延迟300ms和点击穿透问题,vue-awesome-swiper 全局轮播组件设置,babel-polyfill 解决部分手机白屏问题
- // The Vue build version to load with the `import` command
- // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
- import Vue from 'vue'
- import App from './App'
- import router from './router'
- /*
- //reset.css是重置浏览器标签的样式表,其作用就是重新定义标签样式,覆盖浏览器的CSS默认属性,也就是指把浏览器提供的默认样式覆盖掉。
- //
- //在HTML标签在浏览器里有默认的样式,例如 p 标签有上下边距,strong标签有字体加粗样式,em标签有字体倾斜样式。不同浏览器的默认样式之间也会有差别,例如ul默认带有缩进的样式,在IE下,它的缩进是通过margin实现的,而Firefox下,它的缩进是由padding实现的。在切换页面的时候,浏览器的默认样式往往会给我们带来麻烦,影响开发效率。
- //
- //所以解决的方法就是一开始就将浏览器的默认样式全部去掉,更准确说就是通过重新定义标签样式。“覆盖”浏览器的CSS默认属性。最最简单的说法就是把浏览器提供的默认样式覆盖掉!这就是CSS reset。
- */
- import 'styles/reset.css'
- import 'styles/iconfont.css'
-
- /*
- //该css样式用于解决移动端1像素边框问题。问题分析:有些手机的屏幕分辨率较高,是2-3倍屏幕。css样式中border:1px solid red;在2倍屏下,显示的并不是1个物理像素,而是2个物理像素。为了解决这个问题,引入border.css是非常有必要的。
- */
- import 'styles/border.css'
-
- // fastclick 解决移动端click事件延迟300ms和点击穿透问题
- import fastClick from 'fastclick'
- // 轮播图插件
- import VueAwesomeSwiper from 'vue-awesome-swiper'
- import 'swiper/dist/css/swiper.css'
-
- import store from './store/index'
-
- // 解决部分手机白屏问题
- import "babel-polyfill"
-
- fastClick.attach(document.body)
- Vue.config.productionTip = false
- // 全局使用轮播图插件
- Vue.use(VueAwesomeSwiper)
-
- /* eslint-disable no-new */
- new Vue({
- el: '#app',
- router,
- store,
- components: { App },
- template: '<App/>'
- })
6、App.vue 中的 <router-view/> 用于显示路由切换的界面,<keep-alive exclude="Detail"> 标签,作用是缓存 vue ,执行一次 mounted ,exclude 的 vue 则不做缓存
- <template>
- <div id="app">
- <!--缓存数据,执行一次 mounted ,exclude 不包括-->
- <keep-alive exclude="Detail">
- <router-view/>
- </keep-alive>
- </div>
- </template>
-
- <script>
- export default {
- name: 'App'
- }
- </script>
-
- <style>
- </style>
7、pages 是三个路由界面,以及路由界面里面的拆分页面,拆分的目的也是把复杂的界面简单化

8、 Home 界面,包含 5 个拆分页面,Home.vue 主要功能是,从 store 中获取 城市信息,然后axios.get 获取数据,把数据传递给各个子页面,
- <template>
- <div>
- <!--:city='city' 数据向子组件传递-->
- <home-header></home-header>
- <home-swiper :list="swiperList"></home-swiper>
- <home-icons :list="iconsList"></home-icons>
- <home-recommend :list="recommendsList"></home-recommend>
- <home-weekend :list="weekendsList"></home-weekend>
- </div>
- </template>
-
- <script>
- import HomeHeader from './components/Header'
- import HomeSwiper from './components/Swiper.vue'
- import HomeIcons from './components/Icons.vue'
- import HomeRecommend from './components/Recommend.vue'
- import HomeWeekend from './components/Weekend.vue'
-
- // 获取网络数据
- import axios from 'axios'
- import {mapState} from 'vuex'
-
- export default {
- name: 'Home',
- components: {
- HomeHeader,
- HomeSwiper,
- HomeIcons,
- HomeRecommend,
- HomeWeekend
- },
- data (){
- return{
- swiperList:[],
- iconsList:[],
- recommendsList:[],
- weekendsList:[],
- lastCity:'',
- }
-
- },
- computed:{
- ...mapState(['city'])
- },
- methods:{
- getHomeInfo(){
- axios.get('/api/index.json?city='+this.city).then(this.getHomeInfoSucc)
- },
- getHomeInfoSucc(res){
- console.log(res)
- const result = res.data.ret
- if(result && res.data.data){
- const data = res.data.data
- this.swiperList = data.swiperList
- this.iconsList = data.iconsList
- this.recommendsList = data.recommendsList
- this.weekendsList = data.weekendsList
-
- }
- },
- },
-
- mounted(){
- this.lastCity = this.city
- this.getHomeInfo()
- },
- activated(){
- if(this.lastCity !== this.city){
- this.lastCity = this.city
- this.getHomeInfo()
- }
- },
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style>
- * {
- margin: 0px;
- padding: 0px;
- }
- </style>
9、Home 界面中的 Header ,包含输入框,显示当前城市,点击城市可以跳转到城市选择界面
- <template>
- <div class="header">
- <div class="header-left">
- <div class="iconfont back-icon"></div>
- </div>
- <div class="header-input">
- <span class="iconfont"></span>
- 输入城市/景点/游玩主题</div>
- <router-link to="/city">
- <div class="header-right">{{this.city}}
- <span class="iconfont arrow-icon"></span>
- </div>
- </router-link>
- </div>
- </template>
-
- <script>
- // 映射属性
- import {mapState} from 'vuex'
-
- export default {
- name: 'HomeHeader',
- // 接收父组件的数据
- props:{
- },
- computed :{
- ...mapState(['city'])
- },
- data: function(){
- return {}
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
- @import "~styles/varibles.styl";
- .header{
- display: flex;
- line-height: $headerHeight;
- background: $bgColor;
- color: #ffffff;
- }
- .header .header-left{
- width: 0.64rem;
- float: left;
- }
- .header .header-left .back-icon {
- text-align: center;
- font-size: 0.4rem;
- }
- .header .header-input{
- flex: 1;
- background: #fff;
- border-radius: 0.1rem;
- margin-top: 0.12rem;
- margin-left: 0.2rem;
- padding-left: 0.2rem;
- height: 0.64rem;
- line-height: 0.64rem;
- color: #ccc;
- }
- .header .header-right{
- min-width: 1.04rem;
- padding: 0 .1rem;
- float: right;
- text-align: center;
- color:white;
-
- }
-
- .header .header-right .arrow-icon{
- margin-left: -0.04rem;
- font-size: 0.24rem;
-
- }
-
- </style>
10、Home 界面中的 Swiper 是一个轮播组件,轮播图片
- <template>
- <div class="wrapper">
- <swiper :options="swiperOption" v-if="showSwiper">
- <swiper-slide v-for="item of list" :key="item.id">
- <img class="swiper-img" :src="item.url">
- </swiper-slide>
- <div class="swiper-pagination" slot="pagination"></div>
- </swiper>
- </div>
- </template>
-
- <script>
- export default {
- name: 'HomeSwiper',
- props:{
- list:Array
- },
- data: function () {
- return {
- swiperOption: {
- // 下面的圆点
- pagination:'.swiper-pagination',
- // 循环轮播
- loop:true
- },
- }
- },
- computed:{
- showSwiper(){
- return this.list.length
- }
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
- // >>> (scoped 阻挡后 >>>穿透 )
- .wrapper >>> .swiper-pagination-bullet{
- background: #fff;
- }
- .wrapper{
- width: 100%;
- height: 0;
- padding-bottom: 30.45%;
- background: #eee;
- }
- .swiper-img{
- width: 100%;
- }
- </style>
11、Home 界面中的 Icons ,显示icon图标组
- <template>
- <div class="icons">
- <swiper :options="swiperOption">
- <swiper-slide v-for="(page, indexPage) of pages" :key="indexPage">
- <div class="icon" v-for="item of page" :key="item.id">
- <div class="icon-img">
- <img class="icon-img-content" :src="item.imgUrl">
- </div>
- <p class="icon-desc">{{item.desc}}</p>
- </div>
- </swiper-slide>
- </swiper>
- </div>
-
- </template>
-
- <script>
- export default {
- name: 'HomeIcons',
- props:{
- list:Array
- },
- data: function () {
- return {
- swiperOption:{
- autoplay:false
- }
- }
- },
-
- computed:{
- pages (){
- const pages = []
- this.list.forEach((item, index) =>{
- const page = Math.floor(index/8)
- if(pages[page]==null){
- pages[page]=[]
- }
- pages[page].push(item)
- })
- return pages
- }
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
- @import "~styles/varibles.styl";
- @import "~styles/mixins.styl";
- .icons >>> .swiper-wrapper{
- height: 0;
- padding-bottom: 50%;
- }
- .icons{
- margin-top: 0.2rem;
- }
- .icons .icon{
- position: relative;
- overflow: hidden;
- float: left;
- width: 25%;
- padding-bottom: 25%;
- }
- .icons .icon .icon-img{
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: .44rem;
- }
- .icons .icon .icon-img .icon-img-content{
- height: 90%;
- display: block;
- margin: 0 auto;
- }
- .icons .icon .icon-desc{
- position: absolute;
- left: 0;
- right: 0;
- bottom: 0;
- height: .44rem;
- line-height: .44rem;
- color: $darkTextColor;
- text-align: center;
- font-size: 0.2rem;
- /*文字过多,则 ... 显示*/
- ellipsis()
-
- }
- </style>
12、Home 界面中的 Recommend,热门推荐,点击可以跳转到热门推荐的详情界面
- <template>
- <div>
- <div class="title">热门推荐</div>
- <ul class="item-wrapper">
- <!--border-bottom 每个下面有线-->
- <router-link tag="li" class="item border-bottom"
- v-for="item of list"
- :key="item.id"
- :to="/detail/ + item.id"
- >
- <img class="item-img" :src="item.imgUrl">
- <div class="item-info">
- <p class="item-title">{{item.title}}</p>
- <p class="item-desc">{{item.desc}}</p>
- <button class="item-button">查看详情</button>
- </div>
- </router-link>
- </ul>
- </div>
- </template>
-
- <script>
- export default {
- name: 'HomeRecommend',
- props:{
- list:Array
- },
- data: function () {
- return {
-
- }
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
- @import "~styles/mixins.styl";
- .title{
- margin-top: 0.2rem;
- line-height: 0.8rem;
- background: #eee;
- text-indent: 0.2rem;
- }
- .item-wrapper{
- padding: 0.15rem;
- }
- .item{
- overflow: hidden;
- display: flex;
- height: 1.9rem;
- }
- .item .item-info{
- flex:1;
- padding: 0.1rem;
- /*让 ellipsis() 生效*/
- min-width: 0;
- }
- .item .item-img {
- width: 1.7rem;
- height: 1.7rem;
- padding: 0.1rem;
- }
- .item .item-info .item-title{
- line-height: 0.54rem;
- font-size: .32rem;
- ellipsis()
- }
- .item .item-info .item-desc{
- line-height: 0.4rem;
- color: #ccc;
- ellipsis()
- }
- .item .item-info .item-button{
- line-height: .44rem;
- margin-top: 0.2rem;
- background: #ff9300;
- padding: 0 0.2rem;
- border-radius: 0.06rem;
- color: #fff;
- }
- </style>
13、Home 界面中的 Weekend,周末去哪页面
- <template>
- <div>
- <div class="title">周末去哪儿</div>
- <ul class="item-wrapper">
- <!--border-bottom 每个下面有线-->
- <li class="item border-bottom" v-for="item of list" :key="item.id">
- <img class="item-img" :src="item.imgUrl">
- <div class="item-info">
- <p class="item-title">{{item.title}}</p>
- <p class="item-desc">{{item.desc}}</p>
- </div>
- </li>
- </ul>
- </div>
- </template>
-
- <script>
- export default {
- name: 'HomeWeekend',
- props:{
- list:Array
- },
- data: function () {
- return {}
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
- @import "~styles/mixins.styl";
- .title{
- line-height: 0.8rem;
- background: #eee;
- text-indent: 0.2rem;
- }
- .item-wrapper{
- padding: 0.15rem;
- }
- .item {
- overflow: hidden;
- height: 0;
- padding-bottom: 47%;
- }
- .item .item-img {
- width: 100%;
- }
- .item .item-info .item-title{
- line-height: 0.54rem;
- font-size: .32rem;
- ellipsis()
- }
- .item .item-info .item-desc{
- line-height: 0.4rem;
- font-size: 0.2rem;
- color: #ccc;
- ellipsis()
- }
- </style>
14、 City 界面,包含 4 个拆分页面,City.vue 主要功能是,axios.get 获取数据,以及获取 Alphabet传递的数据,对应把数据传递给各个子页面
- <template>
- <div>
- <city-header></city-header>
- <city-search :cities="cities"></city-search>
- <city-list :cities="cities" :hot="hotCities" :letter="letter"></city-list>
- <city-alphabet :cities="cities" @change="handleAlphabetEvent"></city-alphabet>
- </div>
- </template>
-
- <script>
- import CityHeader from './components/Header.vue'
- import CitySearch from './components/Search.vue'
- import CityList from './components/List.vue'
- import CityAlphabet from './components/Alphabet.vue'
- import axios from 'axios'
- export default {
- name: 'City',
- components:{
- CityHeader,
- CitySearch,
- CityList,
- CityAlphabet,
- },
- data: function () {
- return {
- cities:{},
- hotCities:[],
- letter:''
- }
- },
-
- methods:{
- getCityInfo(){
- axios.get('/api/city.json').then(this.getCityInfoSucc)
- },
-
- getCityInfoSucc(res){
- res = res.data
- if(res.ret && res.data){
- const data = res.data
- this.cities = data.cities
- this.hotCities = data.hotCities
- console.log(res)
- }
- },
-
- handleAlphabetEvent(alpha){
- this.letter = alpha
- }
- },
- mounted(){
- this.getCityInfo()
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style>
-
- </style>
15、City 界面中的 Header,包含标题,以及返回键,点击回到 Home 界面
- <template>
- <div class="header">
- 城市选择
- <router-link to="/">
- <div class="iconfont back-icon"></div>
- </router-link>
- </div>
- </template>
-
- <script>
- export default {
- name: 'CityHeader',
- data: function () {
- return {
- msg: 'Welcome to Your Vue.js App'
- }
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
- @import "~styles/varibles.styl";
- .header{
- position: relative;
- overflow: hidden;
- height: $headerHeight;
- line-height: $headerHeight;
- background: $bgColor;
- color: #ffffff;
- text-align: center;
- font-size: 0.4rem;
- }
-
- .header .back-icon{
- position: absolute;
- top:0;
- left: 0;
- width: 0.64rem;
- text-align: center;
- font-size: 0.4rem;
- color: white;
- }
-
-
- </style>
16、City 界面中的 Search,包含输入框,以及搜索出城市的列表,和没有匹配数据的提示;其中搜索功能是在watch 中监听输入的变化,进行在城市名字和拼音中是否包含,添加到结果中去,从而实现搜索功能
- <template>
- <div>
- <div class="search">
- <input v-model="keyword" class="search-input" placeholder="输入城市">
- </div>
- <div v-show="keyword" class="search-content" ref="search">
- <ul>
- <li class="search-item border-bottom"
- v-for="item of list"
- :ket="item.id"
- @click="handleClickCity(item.name)"
- >
- {{item.name}}</li>
- <li class="search-item border-bottom" v-show="hasNoData">没有匹配数据</li>
- </ul>
- </div>
- </div>
- </template>
-
- <script>
- import BScroll from 'better-scroll'
- import {mapActions} from 'vuex'
- export default {
- name: 'CitySearch',
- props:{
- cities:Object
- },
- data: function () {
- return {
- keyword:'',
- list:[],
- timer:null
- }
- },
- watch:{
- keyword(){
- console.log('sdd')
- // 控制执行频率,提高性能
- if(this.timer){
- clearTimeout(this.timer)
- }
- this.timer = setTimeout(()=>{
-
- if(!this.keyword){
- this.list = []
- return
- }
-
- const result = []
- for(let i in this.cities){
- this.cities[i].forEach((value)=>{
- if(value.spell.indexOf(this.keyword) > -1
- || value.name.indexOf(this.keyword)> -1){
-
- result.push(value)
- }
- })
- }
- this.list = result
- console.log('this.scroll ', this.scroll)
- setTimeout(()=>{
- this.scroll.refresh()
- },10)
- },100)
- }
- },
- computed:{
- hasNoData(){
-
- return this.list.length==0
- }
- },
- methods:{
- handleClickCity(city){
- this.changeCity(city)
- this.$router.push('/')
- },
- ...mapActions(['changeCity'])
- },
- mounted(){
- this.scroll = new BScroll(this.$refs.search,{click:true})
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
- @import "~styles/varibles.styl";
- .search{
- height: 0.72rem;
- padding: 0 0.1rem;
- background: $bgColor;
- }
-
- .search .search-input{
- box-sizing: border-box;
- padding: 0 0.1rem;
- width: 100%;
- height: 0.62rem;
- line-height: 0.62rem;
- border-radius: 0.06rem;
- text-align: center;
- color: #777;
- }
- .search-content{
- z-index: 1;
- position: absolute;
- overflow: hidden;
- top:1.58rem;
- bottom: 0;
- right: 0;
- left: 0;
- background: #eee;
- }
-
- .search-content .search-item{
- line-height: 0.62rem;
- padding-left: 0.2rem;
- background: #fff;
- }
-
-
- </style>
17、City 界面中的 List,包含当前城市,热门城市,以及以首字母排列的城市列表,点击热门城市,以及城市列表的城市,都会跳转到对应城市的 Home 界面
- <template>
- <div class="list" ref="wrapper">
- <div>
- <div class="area">
- <div class="title border-topbottom">当前城市</div>
- <div class="button-list">
- <div class="button-wrapper">
- <div class="button">{{this.currentCity}}</div>
- </div>
- </div>
- </div>
- <div class="area">
- <div class="title border-topbottom">热门城市</div>
- <div class="button-list">
- <div class="button-wrapper"
- v-for="item of hot"
- :key="item.id"
- v-on:click="handleClickCity(item.name)"
- >
- <div class="button">{{item.name}}</div>
- </div>
- </div>
- </div>
- <div class="area" v-for="(items,key) of cities" :key="key" :ref="key">
- <div class="title border-topbottom">{{key}}</div>
- <div class="item-list">
- <div class="item border-bottom"
- v-for="item of items"
- :key="item.id"
- v-on:click="handleClickCity(item.name)"
- >
- {{item.name}}</div>
- </div>
- </div>
- </div>
- </div>
- </template>
-
- <script>
- import BScroll from 'better-scroll'
- import {mapState, mapActions} from 'vuex'
- export default {
- name: 'CityList',
- props:{
- hot:Array,
- cities:Object,
- letter:String
- },
- data: function () {
- return {
- }
- },
- computed:{
- ...mapState({
- currentCity:'city'
- })
- },
- watch:{
- letter(){
- if(!this.letter.isEmpty){
- // 监听字母点击,跳转
- console.log(this.letter)
- const element = this.$refs[this.letter][0]
- console.log(element)
- this.scroll.scrollToElement(element)
- }
- }
- },
-
- methods:{
- handleClickCity(city){
- this.changeCity(city)
- this.$router.push('/')
- },
- ...mapActions(['changeCity'])
- },
- mounted(){
- // 城市数据先创建,然后在 scroll 不能可能 scroll 在没有数据的时候构建,从而使得scroll无法滚动
- setTimeout(()=>{
- this.scroll = new BScroll(this.$refs.wrapper,{click:true})
- console.log(' this.scroll ',this.scroll)
- },100)
-
- },
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
- @import "~styles/varibles.styl";
-
- .list{
- overflow: hidden;
- position: absolute;
- top:1.58rem;
- bottom:0;
- left:0;
- right:0;
- }
- .border-topbottom::before{
- border-color: #ccc;
- }
- .border-topbottom::after{
- border-color: #ccc;
- }
- .border-bottom::before{
- border-color: #ccc;
- }
- .title{
- padding-left: 0.2rem;
- background: #eee;
- color: #666;
- line-height: 0.44rem;
- font-size: 0.24rem;
- }
- .button-list{
- overflow: hidden;
- padding: 0.1rem 0.6rem 0.1rem 0.1rem;
- }
- .button-list .button-wrapper{
- width: 33.33%;
- float: left;
- }
- .button-list .button-wrapper .button{
- margin: 0.1rem;
- padding: 0.1rem 0;
- border: 0.02rem solid #ccc;
- text-align: center;
- border-radius: 0.06rem;
- }
- .item-list .item{
- line-height: 0.76rem;
- padding-left: 0.2rem;
- }
-
- </style>
18、City 界面中的 Alphabet,包含所有城市列表的首字母组成的列表,其中通过touch的位置计算出当前选择的哪个字母,传递给 City,City 在传递给 List ,显示对应首字母城市列表
- <template>
- <div class="alphabet">
- <ul>
- <li class="item"
- v-for="item of letters"
- @click="handleOnClick"
- @touchstart.prevent="handleTouchStart"
- @touchmove="handleTouchMove"
- @touchend="handleTouchEnd"
- :ref="item"
- >{{item}}</li>
- </ul>
- </div>
- </template>
-
- <script>
- export default {
- name: 'CityAlphabet',
- props:{
- cities:Object
- },
- data: function () {
- return {
- touchStatus:false,
- aStartY:0,
- // 性能优化
- timer:null
- }
- },
- computed:{
- letters(){
- const letters =[]
- for(let i in this.cities){
- letters.push(i)
- }
- return letters
- }
- },
- updated(){
- this.aStartY = this.$refs['A'][0].offsetTop
- },
-
- methods:{
- handleOnClick(e){
- this.$emit('change', e.target.innerText)
- },
- handleTouchStart(){
- this.touchStatus =true
- },
- handleTouchMove(e){
- if(this.touchStatus){
- if(this.timer){
- clearTimeout(this.timer)
- }
- // 控制执行频率,从而提升性能
- this.timer = setTimeout(()=>{
- // 75 是上面城市 和 输入城市元素的 Y 总和
- const touchY = e.touches[0].clientY - 75
- // 20 是 字母 元素的 Y 值
- const index = Math.floor((touchY - this.aStartY) / 20)
- if(index >= 0 && index < this.letters.length){
- this.$emit('change',this.letters[index])
- }
- }, 16)
-
- }
- },
- handleTouchEnd(){
- this.touchStatus =false
- },
-
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
- @import "~styles/varibles.styl";
-
- .alphabet{
- position: absolute;
- top: 1.58rem;
- right: 0;
- bottom: 0;
- width: 0.4rem;
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
- .alphabet .item{
- color: $bgColor;
- text-align: center;
- line-height: 0.4rem;
- }
-
- </style>
19、 Detail界面,包含 3 个拆分页面,Detail.vue 主要功能是,axios.get 获取数据,对应把数据传递给各个子页面
- <template>
- <div class="detail">
- <detail-banner
- :sightName="sightName"
- :bannerImg="bannerImg"
- :gallaryImgs="gallaryImgs"
- ></detail-banner>
- <detail-header></detail-header>
- <div class="content">
- <detail-list :list="list"></detail-list>
- </div>
- </div>
- </template>
-
- <script>
- import DetailBanner from './components/Banner.vue'
- import DetailHeader from './components/Header.vue'
- import DetailList from './components/List.vue'
- import axios from 'axios'
- export default {
- name: 'Detail',
- components:{
- DetailBanner,
- DetailHeader,
- DetailList,
- },
- data: function () {
- return {
- sightName:'',
- bannerImg:'',
- gallaryImgs:[],
- list: []
- }
- },
- methods:{
- getDetailInfo() {
- // axios.get('/api/detail.json?id='+this.$route.params.id)
- axios.get('/api/detail.json',{
- params:{
- id: this.$route.params.id
- }
- }).then(this.handleGetDataSucc)
-
- },
- handleGetDataSucc(res){
- res = res.data
- if(res.ret && res.data){
- const data = res.data
- this.sightName = data.sightName
- this.bannerImg = data.bannerImg
- this.gallaryImgs = data.gallaryImgs
- this.list = data.categoryList
- }
- }
- },
-
- mounted(){
- this.getDetailInfo()
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
- .content{
- height: 20rem;
- }
- </style>
20、Detail 界面中的 Banner ,包含一个图片展示,以及一个图片集轮播组件(默认隐藏);其中点击图片,就会显示图片集轮播
- <template>
- <div>
- <div class="banner" @click="onClickBanner">
- <img class="banner-img" :src="bannerImg"/>
- <div class="banner-info">
- <div class="banner-title">{{this.sightName}}</div>
- <div class="banner-number"><span class="iconfont banner-icon"></span>78</div>
- </div>
- </div>
- <fade-animation>
- <common-gallary
- :imgs="gallaryImgs"
- @close="handleGallaryClose"
- v-show="isShowGallary"
- ></common-gallary>
- </fade-animation>
- </div>
- </template>
-
- <script>
- import CommonGallary from 'common/gallary/Gallary'
- import FadeAnimation from 'common/fade/FadeAnimation'
- export default {
- name: 'DetailBanner',
- props:{
- sightName:String,
- bannerImg:String,
- gallaryImgs:Array,
- },
- components:{
- CommonGallary,
- FadeAnimation,
- },
- data: function () {
- return {
- isShowGallary: false,
- }
- },
- methods:{
- onClickBanner(){
- this.isShowGallary = true
- },
- handleGallaryClose(){
- this.isShowGallary = false
- },
- },
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
- .banner{
- position: relative;
- overflow: hidden;
- height: 0;
- padding-bottom: 50%;
- background: green;
- }
-
- .banner .banner-img{
- width: 100%;
- }
-
- .banner .banner-info{
- display: flex;
- position: absolute;
- left: 0;
- right: 0;
- bottom: 0;
- line-height: 0.6rem;
- color: #fff;
- background-image: linear-gradient(top,rgba(0,0,0,0),rgba(0,0,0,0.8));
- }
-
- .banner .banner-info .banner-title{
- flex: 1;
- font-size: 0.32rem;
- padding: 0 0.2rem;
- }
-
- .banner .banner-info .banner-number{
- height: 0.32rem;
- line-height: 0.32rem;
- padding: 0 0.4rem;
- margin-top: 0.14rem;
- border-radius: 0.2rem;
- background: rgba(0,0,0,0.8);
- font-size: 0.24rem;
- }
-
- .banner .banner-info .banner-number .banner-icon{
- font-size: 0.24rem;
- padding: 0.1rem;
- }
- </style>
21、Detail 界面中的 Header,包含绝对位置的返回按钮,以及一个固定位置的返回按钮标题;根据当前页面的滚动情况,动态切换不同位置的按钮显隐
- <template>
- <div class="header">
- <router-link tag="div" to='/' class="header-abs" v-show="showAbs">
- <div class="iconfont back-abs-icon"></div>
- </router-link>
- <router-link tag="div" to='/' class="header-fixed" v-show="!showAbs"
- :style="styleOpacity"
- >
- <div class="iconfont back-fixed-icon"></div>
- 景点详情
- </router-link>
- </div>
- </template>
-
- <script>
- export default {
- name: 'DetailHeader',
- data: function () {
- return {
- showAbs:true,
- styleOpacity:{
- opacity:0
- }
- }
- },
-
- methods:{
- handleScroll()
- {
- console.log('handleScroll')
- const top = document.documentElement.scrollTop
- console.warn('top ', top)
- if (top > 60) {
- this.showAbs = false
- const opacity = top/140 > 1? 1: top/140
- this.styleOpacity = {
- opacity,
- }
- } else {
- this.showAbs = true
- this.styleOpacity = {
- opacity:0,
- }
- }
- },
- },
- mounted(){
- window.addEventListener('scroll',this.handleScroll)
- },
- unmounted(){
- window.removeEventListener('scroll',this.handleScroll)
- },
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
- @import "~styles/varibles.styl";
-
- .header-abs{
- position: absolute;
- left:0.2rem;
- top:0.2rem;
- width: 0.8rem;
- height: 0.8rem;
- line-height: 0.8rem;
- text-align: center;
- border-radius: 0.4rem;
- background: rgba(0,0,0,0.8);
- }
- .header-abs .back-abs-icon{
- color: #fff;
- font-size: 0.4rem;
-
- }
-
- .header-fixed{
- position: fixed;
- z-index: 2;
- top:0;
- left:0;
- right:0;
- overflow: hidden;
- height: $headerHeight;
- line-height: $headerHeight;
- background: $bgColor;
- color: #ffffff;
- text-align: center;
- font-size: 0.4rem;
- }
-
- .header-fixed .back-fixed-icon{
- position: absolute;
- top:0;
- left: 0;
- width: 0.64rem;
- text-align: center;
- font-size: 0.4rem;
- color: white;
- }
- </style>
22、Detail 界面中的 List,一个简单的列表信息展示
- <template>
- <div class="list">
- <div class="item" v-for="(item,id) of list" :key="id">
- <div class="item-title border-bottom">
- <span class="item-title-icon"></span>
- {{item.title}}</div>
- <div v-if="item.children" class="item-children">
- <detail-list :list="item.children"></detail-list>
- </div>
- </div>
-
- </div>
- </template>
-
- <script>
- export default {
- name: 'DetailList',
- props:{
- list:Array
- },
- data: function () {
- return {
- msg: 'Welcome to Your Vue.js App'
- }
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style lang="stylus" scoped>
-
- .item-title-icon{
- position: relative;
- left: .06rem;
- top: .06rem;
- display: inline-block;
- width: .36rem;
- height: .36rem;
- background: url(http://s.qunarzz.com/piao/image/touch/sight/detail.png) 0 -.45rem no-repeat;
- margin-right: .1rem;
- background-size: .4rem 3rem;
- }
- .item-title{
- line-height: 0.8rem;
- font-size: 0.32rem;
- padding: 0 0.2rem;
- }
- .item-children{
- padding: 0 0.4rem;
- }
- </style>
1、router路由管理,三个路由 path(/ 、/city、/detail/id),并且添加路由地址切换回 scroll 都置于顶部处理
- import Vue from 'vue'
- import Router from 'vue-router'
- import Home from '@/pages/home/Home'
- import City from '@/pages/city/City'
- import Detail from '@/pages/detail/Detail'
-
- Vue.use(Router)
-
- /**
- * 路由
- */
- export default new Router({
- routes: [
- {
- path: '/',
- name: 'Home',
- //component: Home
- // 异步加载组件,避免大量的代码堆积到 app.js 中,()=> import( '@/pages/city/City') 可以拆分代码到其他的projectX.js中
- component: ()=> import( '@/pages/home/Home')
- },
- {
- path: '/city',
- name: 'City',
- //component: City
- component: ()=>import('@/pages/city/City')
- },
- {
- //动态路由
- path: '/detail/:id',
- name: 'Detail',
- //component: Detail
- component: ()=>import('@/pages/detail/Detail')
- },
- ],
-
- //添加路由切换的时候,scroll ,都回到顶部处理
- scrollBehavior(to, from, savedPosition) {
- // 回到顶部
- return { x: 0, y:0 }
- },
-
-
- })
2、store 添加对某个全局参数的管理

- state.js
-
- /**
- * Created by 12722 on 2022/6/13.
- */
- let defaultCity = '桂林'
- try{
- if (localStorage.city){
- defaultCity = localStorage.city
- }
- }
- catch(e){}
-
- export default{
- city:defaultCity
- }
-
-
-
- action.js
- /**
- * Created by 12722 on 2022/6/13.
- */
- export default {
- changeCity(context,city){
- context.commit('changeCity',city)
- }
- }
-
-
-
- mutations.js
- /**
- * Created by 12722 on 2022/6/13.
- */
- export default {
- changeCity(state, city){
- state.city = city
- try{ // 本地化保存
- localStorage.city = city}
- catch(e){}
-
- }
- }
1、App.vue 中的 <keep-alive exclude="Detail"></keep-alive>
keep-alive 会缓存一些数据,在界面切换后,也不销毁,例如mounted函数中 axios.get数据,从而避免 axios.get 频繁访问

2、setTimeout 函数的使用,例如在 City 界面中 Search.vue 进行输入搜索时,可以控制搜索频率,间接提高性能;以及类似在 City 界面中 Alphabet.vue 进行 Touch 选中 字母的时候,也可控制搜索频率,间接提高性能


1、例如 Home.vue 中 mounted 使用 axios.get 获取服务端数据时
使用 axios.get('/api/index.json?city='+this.city).then(this.getHomeInfoSucc) 中的
/api/index.json?city='+this.city 在浏览器中获取不到数据,为什么 axios.get 可以获得呢

2、其实,在 config/index.js 做代理配置,当遇到 /api 就会进行对应转换,从而使得 axios.get 获取到对应的数据

3、axios.get 获取的数据 static/mock 文件夹下的数据

1、Gallary.vue 全屏大图滑动轮播图片
只要引入Gallary.vue,对应的添加传输图片列表数据,并且添加对应关闭事件,就可以轻易实现全屏大图滑动轮播图片

参考引用 detail/components/Banner.vue :

2、FadeAnimation.vue 渐隐渐现动画效果
只要引入FadeAnimation.vue 包裹需要渐隐渐现的元素即可

参考引用 detail/components/Banner.vue :


1、mixins.styl 功能是:字显示超出范围,则用三个点... 表示,例如 ‘字太多了...’

仅供学习参考使用:
代码运行(最好的运行端口为 8080):
1、npm install
2、npm run start