vue實現城市列表選擇

小周sri的碼農發表於2018-07-16

成果展示

最後的成果就是下面所展示的內容,因為gif圖沒有做,只能截圖所展示,接下來,會帶著大家一步一步的完成下面功能,腳手架搭建和node安裝在本次案例不會講解,如果瞭解,可以在我的部落格園找到有詳細介紹

準備工作:

 引入axios外掛,呼叫better-scroll第三方外掛,本地json檔案,可以參考目錄中的city.json,有條件的也可以自己去扒

功能分析

1.獲取json資料展示城市列表 。

2.側邊字母定位滾動到相應的位置。

3.實現搜尋城市

接下來我們開始對元件進行劃分:本次案例中,總共劃分為五個元件,下面就是元件的劃分圖

建立city元件,通過父元件獲取資料,傳遞給子元件

<template>
    <div class="city">
        <CityHeader></CityHeader>   //頭部
        <Search :list="cities"></Search>  //搜尋
        <List :hot="hotCity" :letter="letter" :list="cities"></List> //城市列表
        <Alphabet @chang="handleLetterChang" :list="cities"></Alphabet>  //A-Z
    </div>
</template>

<script>
import axios from 'axios'
import CityHeader from './components/Header'
import Search from './components/Search'
import List from './components/List'
import Alphabet from './components/Alphabet'
export default {
    data () {
        return {
            cities:{}, // 城市列表
            hotCity:[], //熱門城市
            letter: ''  // A-Z
        }
    },
    components: {
        CityHeader,
        Search,
        List,
        Alphabet
    },
    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.hotCity = data.hotCities
                this.cities = data.cities
            }
            console.log(this.cities)
        },
        handleLetterChang(letter) { //接受子元件傳過來的
//            console.log(letter)
            this.letter = letter
        }
    },
    mounted () {
        this.getCityInfo ()
    }
}
</script>

<style scoped lang="stylus">

</style>
City元件

把得到的資料分次傳遞個對應的子元件,這樣有利於網站優化,不用頻繁的請資料

<template>
    <div class="city">
        <CityHeader></CityHeader>
        <Search :list="cities"></Search>
        <List :hot="hotCity" :letter="letter" :list="cities"></List>
        <Alphabet @chang="handleLetterChang" :list="cities"></Alphabet>
    </div>
</template>
export default {
    data () {
        return {
            cities:{}, // 城市列表
            hotCity:[], //熱門城市
            letter: ''  // A-Z
        }
    },
    components: {
        CityHeader,
        Search,
        List,
        Alphabet
    },
    methods:{
        getCityInfo () {
            axios.get('/api/city.json').then(this.getCityInfoSucc)   //請求本地配置的mock資料
        },
        getCityInfoSucc(res){
             res = res.data
            if (res.ret && res.data) {
                const data = res.data
                this.hotCity = data.hotCities
                this.cities = data.cities
            }
        }
    },
    mounted () {
        this.getCityInfo ()
    }
}

 

建立頭部元件,

<template>
    <div class="header">
        城市選擇
        <router-link to="/">
            <div class="iconfont back-icon">&#xe624;</div>
        </router-link>
    </div>
</template>

<script>
export default {
    
}
</script>

<style scoped lang="stylus">
@import '~styles/varibles.styl';
@import '~styles/mixins.styl';
.header
    overflow: hidden
    height $headerHeight
    line-height: $headerHeight
    text-align: center
    color: #fff
    background: $bgColor
    font-size: .4rem
    .back-icon
        position: absolute
        left: 0
        top: 0
        width: .64rem
        font-size: .4rem
        text-align: center
        color: #fff
</style>

建立搜尋元件頁面,接受父元件傳遞的資料,引入better-scroll第三方外掛,實現列表滾動

<template>
    <div>
        <div class="search">
            <input v-model="keyword" class="search-input" type="text" placeholder="輸入城市名或者拼音" />
        </div>
        <div class="search-content" ref="search" v-show="keyword">
            <ul>
                <li class="serach-item border-bottom" v-for="item in listItem" :key="item.id">{{item.name}}</li>
                <li v-show="hasNoData" class="serach-item border-bottom">沒有搜尋到匹配的資料</li>
            </ul>
        </div>
    </div>
</template>
<script>
import BScroll from 'better-scroll'
export default {
    props: {
          list: Object,
    },
    data() {
        return {
            keyword:'',
            listItem:[],
            timer:null
        }
    },
    computed: {
        hasNoData() {
            return !this.listItem.length  //沒有搜尋的條件是否顯示
        }
    },
    watch: {
        keyword () {
            if (this.timer) {
                clearTimeout(this.timer)
            }
            if(!this.keyword) {  //清空
                this.listItem = ""
                return
            }
            this.timer = setTimeout(() => {
                const result = []
                for (let i in this.list) {
                    this.list[i].forEach((value) => {   //匹配搜尋的條件
                        if (value.spell.indexOf(this.keyword) > -1 || value.name.indexOf(this.keyword) > -1) {
                            result.push(value)
                        }
                    })
                }
                this.listItem= result
            },100)
        }
    },
    mounted () {
        this.scroll = new BScroll(this.$refs.search)
    }
}
</script>

<style scoped lang="stylus">
@import '~styles/varibles.styl'
@import '~styles/mixins.styl'
.search
    height: .72rem
    padding: 0 .1rem
    background:$bgColor
    .search-input
        box-sizing: border-box
        width:100%
        height: .62rem
        line-height: .62rem
        text-align: center
        border-radius: .06rem
        padding: 0 .1rem
        color: #666
.search-content 
    z-index: 1
    overflow:hidden
    position:absolute
    top: 1.58rem
    left: 0
    right: 0
    bottom: 0
    background: #eee
    .serach-item
        line-height: .62rem
        padding-left:.2rem
        color:#666
        background: #fff
</style>

建立城市列表元件,引入better-scroll外掛,實現列表滾動,通過watch監聽letter,實現字母與城市列表滾動

<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">鄭州</div>
                    </div>
                </div>
            </div>
            <div class="area">
                <div class="title border-topbottom">熱門城市</div>
                <div class="button-list">
                    <div class="button-wrapper" v-for="item in hot" :key="item.id">
                        <div class="button">{{item.name}}</div>
                    </div>
                </div>
            </div>
            <div class="area" 
                v-for="(item,key) in list" 
                :ref="key"
                :key="key">
                <div class="title border-topbottom">{{key}}</div>
                <ul class="item-list">
                    <li class="item border-bottom"
                         v-for="listInner in item"
                          :key="listInner.id"
                    >{{listInner.name}}</li>
                </ul>
            </div>
        </div>
    </div>
</template>

<script>
import BScroll from 'better-scroll'
export default {
    props: {
          hot: Array,
          list: Object,
          letter:String
      },
    mounted () {
        this.scroll = new BScroll(this.$refs.wrapper)
    },
    watch:{
        letter () {  //監聽列表滾動事件 A-Z
            if(this.letter) {
                const element = this.$refs[this.letter][0]
                this.scroll.scrollToElement(element)
            }
        }
    }
}
</script>

<style scoped lang="stylus">
@import '~styles/varibles.styl';
@import '~styles/mixins.styl';
.border-topbottom
    &:before
        background: #ccc
    &:after
        background:#ccc
.border-bottom
    &:before
        background: #ccc
.list
    overflow: hidden
    position:absolute
    top:1.58rem
    left:0
    right:0
    bottom:0
    .title
        line-height: .54rem;
        background: #eee;
        padding-left: .2rem;
        color: #666;
        font-size: .26rem;
    .button-list
        overflow:hidden
        padding: .1rem .6rem .1rem .1rem
        .button-wrapper
            float:left
            width:33.33%
            .button
                margin: .1rem
                padding: .1rem 0
                text-align: center
                border: .02rem solid #ccc
                border-radius: .06rem
    .item-list
        .item
            line-height: .76rem
            color:#212121
            padding-left: .2rem
            font-size: .28rem
            text-overflow: ellipsis
            white-space: nowrap
</style>

建立字母元件,點選字母,左邊列表城市想對應,通過this.$emit事件,子元件在觸發的事件傳遞給父元件,父元件通過子元件傳遞的事件,在傳遞給List元件,

<template>
    <div class="list">
        <li class="item"
            :ref="item"
             @click="handeClick" 
             @touchstart="handleTouchStart" 
             @touchmove="handleTouchMove" 
             @touchend= "handleTouchEnd"
             v-for="item of letter" 
             :key="item">{{item}}</li>
    </div>
</template>

<script>
export default {
    props: {
          list: Object
    },
    data () {
        return {
            touchstart:false,
            startY:0,
            timer: null
        }
    },
    updated () {
        this.startY = this.$refs['A'][0].offsetTop
    },
    computed: {
        letter () {
            const letter =[]
            for (let i in this.list) { //迴圈A-Z
                letter.push(i)
            }
            return letter
        }
    },
    methods: {
        handeClick(e) {
            this.$emit('chang',e.target.innerText) //傳給父元件City
        },
        handleTouchStart () {
            // 手指放上
            this.touchstart = true
        },
        handleTouchMove (e) {
            // 手指移動
            if(this.touchstart) {
                if(this.timer) {
                    clearInterval(this.timer)
                }
                this.timer = setTimeout(() => {
                    const touchY = e.touches[0].clientY -79   //到藍色頭部的距離
                    const index = Math.floor((touchY - this.startY ) / 20)
                    if(index >=0 && index < this.letter.length) {
                        this.$emit('chang',this.letter[index])
                    }
                },16)
            }
        },
        handleTouchEnd () {
            // 手指離開
            this.touchstart = false
        }
    }
}
</script>

<style scoped lang="stylus">
@import '~styles/varibles.styl';
@import '~styles/mixins.styl';
.list
    display: flex
    flex-direction:column
    justify-content: center
    position:absolute
    top: 1.58rem
    right: 0
    bottom: 0
    width: .4rem
    .item
        line-height:.44rem
        text-align: center
        color: $bgColor
        list-style:none
</style>

 以上都是所有本次的內容,如果喜歡可以關注一下

相關文章