zoukankan      html  css  js  c++  java
  • vue搜索页开发(热门搜索,历史搜索,淘宝接口演示)

    完整效果演示

    首先完成这个伪搜索框

    src/components/search/index.vue (通用搜索框组件)

    <template>
      <div class="mine-search-box-wrapper">
        <i class="iconfont icon-search"></i>
        <div class="mine-search-box" v-if="fake">{{placeholder}}</div>
        <input
          class="mine-search-box"
          type="text"
          title="搜索框"
          :placeholder="placeholder"
          ref="input"
          v-model="query"
          v-if="!fake"
        >
        <i
          class="iconfont icon-close"
          v-show="query"
          @click="reset"
        ></i>
      </div>
    </template>
    
    <script>
    import {debounde} from 'assets/js/util';
    
    
    export default {
        name:'Search',
        props:{//接收的参数
            placeholder:{
                type:String,
                default:'请输入搜索内容'
            },
            fake:{
                type:Boolean,
                default:false
            }
        },
        data(){
            return{
                query:'',
            }
        },
        watch:{
            query:debounde(function(){
                this.$emit('query',this.query);
            })
        },
        methods:{
            focus(){
                this.$refs.input && this.$refs.input.focus();
            },
            clear(){
                this.query='';
            },
            reset(){//重置
                this.clear();
                this.focus();
            }
        }
    }
    </script>
    
    <style lang="scss" scoped>
        $search-box-height: 30px;
        $icon-color: #ccc;
        $icon-font-size-sm: 18px;
    
      .mine-search-box-wrapper {
        display: flex;
        align-items: center;
         85%;
        height: $search-box-height;
        padding: 0 7px;
        background-color: #fff;
        border-radius: $search-box-height / 2;
        margin-left:15px;
      }
    
      .iconfont {
        color: $icon-color;
        font-size: $icon-font-size-sm;
        font-weight: bold;
      }
    
      .mine-search-box {
        flex: 1;
        background: none;
        border: none;
        margin: 0 6px;
        color: #666;
        line-height: 1.5;
      }
    </style>

    src/assets/js/util.js  节流函数(防止请求数据时频率过快消耗性能)

    //函数节流
    export const debounde=(func,delay=200)=>{
        let timer=null;
    
        return function(...args){
            timer && clearTimeout(timer);
    
            timer=setTimeout(()=>{
                func.apply(this,args);
            },delay);
        }
    }

    在分类页的头部组件中引入搜索框组件

    src/pages/category/header.vue

    <template>
        <div class="header">
            <i class="iconfont icon-scan header-left"></i>
            <div class="header-center">
                <search placeholder="开学季有礼,好货五折起" @query='getQuery' fake @click.native="goToSearch" />
            </div>
            <i class="iconfont icon-msg header-right"></i>
        </div>
    </template>
    
    <script>
    import Search from 'components/search';
    
    export default {
        name:'CategoryHeader',
        components:{
            Search
        },
        methods:{
            getQuery(query){
                console.log(query);
            },
            goToSearch(){
                this.$router.push('/search');
            }
        }
    }
    </script>
    
    <style lang="scss" scoped>
        .header{
            background-color:rgba(222, 24, 27, 0.9);
            transition:background-color 0.5s;
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding:5px 20px;
    
            .iconfont{
                font-size:24px;
                color:#fff;
            }
    
            .header-center{
                flex:1;
            }
        } 
    </style>

    点击搜索框之后会跳转到真正的搜索页

    热门搜索组件

    src/pages/search/hot.vue

    <template>
      <div class="hot">
        <h4 class="hot-title">热门搜索</h4>
        <div class="loading-container" v-if="!hots.length">
          <me-loading/>
        </div>
        <ul class="hot-list" v-else>
          <li class="hot-item" v-for="(item,index) in hots" :key="index" @click="$_selectItem(item.hotWord)">
              {{item.hotWord}}
          </li>
        </ul>
      </div>
    </template>
    
    <script>
    import Search from 'components/search';
    import MeLoading from 'components/loading';
    import {getHot} from 'api/search';
    import {searchMixin} from 'api/mixins';
    
    export default {
        name:'SearchHot',
        components:{
            MeLoading
        },
        data(){
            return{
                hots:[]
            }
        },
        mixins:[searchMixin],
        created(){
            this.getHot().then(()=>{
                this.$emit('loaded');
            })
        },
        methods:{
           getHot(){
               return getHot().then(data=>{
                   return new Promise(resolve=>{
                       if(data){
                           this.hots=data;
                           resolve();
                       }
                   })
               })
           }
        }
    }
    </script>
    
    <style lang="scss" scoped>
    $border-color: #e5e5e5;
    $font-size-base: 12px;
    $font-size-l: $font-size-base + 2;
    
     .hot {
        padding-left: 10px;
        background-color: #fff;
        border-bottom: 1px solid $border-color;
        margin-bottom: 10px;
    
        &-title {
          height: 34px;
          line-height: 34px;
          font-size: $font-size-l;
          font-weight: bold;
        }
    
        &-list {
          display: flex;
          flex-wrap: wrap;
        }
    
        &-item {
          padding: 8px;
          background-color: #f0f2f5;
          border-radius: 4px;
          margin: 0 10px 10px 0;
          color: #686868;
        }
      }
    
      .loading-container {
        padding: 10px 0;
      }
    </style>

    axios获取热门搜索数据

    src/api/search.js

    import axios from 'axios';
    
    //获取热门搜索数据 ajax
    export const getHot=()=>{
        return axios.get('http://www.imooc.com/api/search/hot').then(res=>{
            
            res=res.data.hotKeyWord;
            if(res && res.owner){
                return res.owner;
            }
            throw new Error('没有成功获取到数据');
    
        }).catch(err=>{
            console.log(err);
        });
    }

    点击搜索的关键词,跳转到淘宝搜索程序

    src/api/mixins.js

    import storage from 'assets/js/storage';
    import {SEARCH_HISTORY_KEYWORD_KEY} from 'pages/search/config';
    
    export const searchMixin={
        methods:{
            $_selectItem(keyword){
                let keywords=storage.get(SEARCH_HISTORY_KEYWORD_KEY,[]);//找到所有搜索历史
     
                 if(keywords.length!=0){
                     keywords=keywords.filter(val=>val!=keyword);//这次的关键词如果在搜索历史里已存在,先剔除掉
                 }
     
                 keywords.unshift(keyword);//把这次的关键词放在搜索历史的最开头
                
                storage.set(SEARCH_HISTORY_KEYWORD_KEY,keywords);//更新搜索历史
     
                //跳转到淘宝搜索页
                location.href = `https://s.m.taobao.com/h5?event_submit_do_new_search_auction=1&_input_charset=utf-8&topSearch=1&atype=b&searchfrom=1&action=home%3Aredirect_app_action&from=1&sst=1&n=20&buying=buyitnow&q=${keyword}`;
            }
        }
    }

    本地存储文件 assets/js/storage.js

    const storage = window.localStorage;
    
    export default {
      set(key, val) {
        if (val === undefined) {
          return;
        }
        storage.setItem(key, serialize(val));
      },
      get(key, def) {
        const val = deserialize(storage.getItem(key));
        return val === undefined ? def : val;
      },
      remove(key) {
        storage.removeItem(key);
      },
      clear() {
        storage.clear();
      }
    };
    
    function serialize(val) {
      return JSON.stringify(val);
    }
    
    function deserialize(val) {
      if (typeof val !== 'string') {
        return undefined;
      }
      try {
        return JSON.parse(val);
      } catch (e) {
        return val || undefined;
      }
    }

    搜索页配置文件 src/pages/search/config.js

    const prefix = 'mall-search';
    const suffix = 'key';
    export const SEARCH_HISTORY_KEYWORD_KEY = `${prefix}-history-keyword-${suffix}`;

    历史搜索组件

    src/pages/search/history.vue

    <template>
      <div class="history" v-if="historys.length">
        <h4 class="history-title">历史搜索</h4>
        <transition-group class="g-list" tag="ul" name="list">
          <li class="g-list-item" v-for="item in historys" :key="item" @click="$_selectItem(item)">
              <span class="g-list-text">{{item}}</span>
              <!-- .stop 禁止事件冒泡 -->
              <i class="iconfont icon-delete" @click.stop="removeItem(item)"></i>
          </li>
        </transition-group>
        <a href="javascript:;" class="history-btn" @click="showConfirm">
            <i class="iconfont icon-clear" ></i>
            清空历史搜索
        </a>
      </div>
    </template>
    
    <script>
    import storage from 'assets/js/storage';
    import {SEARCH_HISTORY_KEYWORD_KEY} from 'pages/search/config';
    import {searchMixin} from 'api/mixins';
    
    
    export default {
        name:'SearchHistory',
        data(){
            return{
                historys:[]
            }
        },
        mixins:[searchMixin],
        created(){
            this.getKeyword();
        },
        methods:{
            update(){
              this.getKeyword();
            },
            getKeyword(){
                this.historys=storage.get(SEARCH_HISTORY_KEYWORD_KEY,[]);
                this.$emit('loaded');
            },
            removeItem(item){
              this.historys=this.historys.filter(val=>val!==item);//点击后删除该项
              storage.set(SEARCH_HISTORY_KEYWORD_KEY,this.historys);//更新缓存
              this.$emit('remove-item');
            },
            showConfirm(){
              this.$emit('show-confirm');
            },
            clear(){
              storage.remove(SEARCH_HISTORY_KEYWORD_KEY);
            }
        }
    }
    </script>
    
    <style lang="scss" scoped>
    
        $border-color: #e5e5e5;
        $font-size-base: 12px;
        $font-size-l: $font-size-base + 2;
        $border-color: #e5e5e5;
    
        @mixin flex-center($direction: row) {
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: $direction;
        }
    
      .history {
        padding-bottom: 30px;
        background-color: #fff;
    
        &-title {
          height: 34px;
          line-height: 34px;
          padding: 0 10px;
          font-size: $font-size-l;
          font-weight: bold;
        }
    
        &-btn {
          @include flex-center();
           80%;
          height: 40px;
          background: none;
          border: 1px solid #ccc;
          border-radius: 4px;
          margin: 0 auto;
          color: #686868;
    
          .iconfont {
            margin-right: 5px;
          }
        }
      }
    
      .g-list {
        border-top: 1px solid $border-color;
        border-bottom: 1px solid $border-color;
        margin-bottom: 20px;
      }
    
      .list {
        &-enter-active,
        &-leave-active {
          transition: height 0.1s;
        }
    
        &-enter,
        &-leave-to {
          height: 0;
        }
      }
    
    </style>

    列表样式统一抽离出去

    src/assets/scss/_list.scss

    // list
    @mixin flex-between() {
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
    
    //ellipsis
    @mixin ellipsis() {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
    
      $border-color: #e5e5e5;
      
    .g-list {
      padding-left: 10px;
    }
    .g-list-item {
      overflow: hidden;
      @include flex-between();
      height: 44px;
      padding-right: 10px;
      border-bottom: 1px solid $border-color;
      color: #686868;
    
      &:last-child {
        border-bottom: none;
      }
    }
    .g-list-text {
      flex: 1;
      line-height: 1.5;
      @include ellipsis();
    }

    src/assets/scss/index.scss

    @import 'icons';
    @import 'list';
    
    *{
        margin:0;
        padding:0;
    }
    html,body{
        // 必须设置,否则内容滚动效果无法实现
        100%;
        height:100%;
    }
    ul,li{
        list-style:none;
    }
    a{
        text-decoration: none;
        color:#333;
    }

    确认框组件

    src/components/comfirm/index.vue

    <template>
        <transition name="mine-confirm">
            <div class="mine-confirm-wrapper" v-show="visible">
                <div class="mine-confirm">
                    <div class="mine-confirm-title">{{title}}</div>
                    <div class="mine-confirm-msg">{{msg}}</div>
                    <div class="mine-confirm-btns">
                        <button class="mine-confirm-btn mine-confirm-cancel" @click="cancel">{{cancelBtnText}}</button>
                        <button class="mine-confirm-btn mine-confirm-ok" @click="confirm">{{confirmBtnText}}</button>
                    </div>
                </div>
            </div>
        </transition>
    </template>
    
    <script>
    export default {
        name:'MineConfirm',
        props:{
            title:{
                type:String,
                default:''
            },
            msg:{
                type:String,
                default:'确定执行此操作吗?'
            },
            cancelBtnText:{
                type:String,
                default:'取消'
            },
            confirmBtnText:{
                type:String,
                default:'确定'
            }
        },
        data(){
            return{
                visible:false
            }
        },
        methods:{
            show(){
                this.visible=true;
            },
            hide(){
                this.visible=false;
            },
            cancel(){
                this.hide();
                this.$emit('cancel');
            },
            confirm(){
                this.hide();
                this.$emit('confirm');
            }
        }
    }
    </script>
    
    <style lang="scss" scoped>
    
    $search-z-index: 1200;
    $search-popup-z-index: $search-z-index + 10;
    $modal-bgc: rgba(0, 0, 0, 0.4);
    
    @mixin flex-center($direction: row) {
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: $direction;
    }
    @mixin ellipsis() {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    
      .mine-confirm-wrapper {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        z-index: $search-popup-z-index;
        @include flex-center();
        background-color: $modal-bgc;
      }
    
      .mine-confirm {
        overflow: hidden;
         88%;
        background-color: #fff;
        border-radius: 10px;
        font-size: 16px;
    
        &-title {
          padding: 20px 15px 0;
          font-size: 18px;
          text-align: center;
          @include ellipsis();
    
          & + .mine-confirm-msg {
            padding-top: 20px;
            padding-bottom: 20px;
          }
        }
    
        &-msg {
          padding: 40px 15px;
          text-align: center;
          line-height: 1.5;
        }
    
        &-btns {
          display: flex;
        }
    
        &-btn {
          flex: 1;
          height: 44px;
          line-height: 44px;
          background: none;
          border: none;
        }
    
        &-cancel {
          border-top: 1px solid #e3e5e9;
        }
    
        &-ok {
          background-color: #f23030;
          color: #fff;
        }
      }
    
      .mine-confirm {
        &-enter-active,
        &-leave-active {
          transition: opacity 0.3s;
        }
    
        &-enter,
        &-leave-to {
          opacity: 0;
        }
    
        &-enter-active {
          .mine-confirm {
            animation: bounce-in 0.3s;
          }
        }
      }
    
      // https://cn.vuejs.org/v2/guide/transitions.html#CSS-动画
      @keyframes bounce-in {
        0% {
          transform: scale(0);
        }
        50% {
          transform: scale(1.1);
        }
        100% {
          transform: scale(1);
        }
      }
    </style>

     搜索结果页

    src/pages/search/result.vue

    <template>
        <div class="result">
        <div class="loading-container" v-show="loading">
          <me-loading/>
        </div>
        <ul class="g-list" v-show="!loading && results.length">
          <li
            class="g-list-item"
            v-for="(item, index) in results"
            :key="index"
            @click="$_selectItem(item[0])"
          >
            <span class="g-list-text">{{item[0]}}</span>
          </li>
        </ul>
        <div class="no-result" v-show="!loading && !results.length">没有结果</div>
      </div>
    </template>
    
    <script>
      import MeLoading from 'components/loading';
      import {getSearchResult} from 'api/search';
      import {searchMixin} from 'api/mixins';
    
    export default {
        name:'SearchResult',
        components:{
            MeLoading
        },
        data(){
            return{
                results:[],
                loading:false
            }
        },
        props:{
            query:{
                type:String,
                default:''
            }
        },
        mixins:[searchMixin],
        watch:{
            query(query){
                
                this.getResults(query);
            }
        },
        methods:{
            getResults(keyword){
                if(!keyword){
                    return;
                }
    
                this.loading=true;
                getSearchResult(keyword).then(data=>{
                    console.log(data);
                    if(data){
                        
                        this.results=data;
                        this.loading=false;
                    }
                })
            }
        }
    }
    </script>

    修改src/api/search.js

    import axios from 'axios';
    import jsonp from 'assets/js/jsonp';
    
    //获取热门搜索数据 ajax
    export const getHot=()=>{
        return axios.get('http://www.imooc.com/api/search/hot').then(res=>{
            
            res=res.data.hotKeyWord;
            if(res && res.owner){
                return res.owner;
            }
            throw new Error('没有成功获取到数据');
    
        }).catch(err=>{
            console.log(err);
        });
    }
    
    //获取搜索框的搜索结果
    export const getSearchResult=keyword=>{
        const url='https://suggest.taobao.com/sug';
        
        const params={
            q:keyword,
            code:'utf-8',
            area:'c2c',
            nick:'',
            sid:null
        };
        //https://suggest.taobao.com/sug?q=apple&code=utf-8&area=c2c&nick=&sid=null&callback=jsonp5
        return jsonp(url, params, {
            param: 'callback'
          }).then(res => {
             console.log(res);
            if (res.result) {
                // console.log(res);
                return res.result;
            }
    
            throw new Error('没有成功获取到数据!');
        }).catch(err => {
            if (err) {
                console.log(err);
            }
        });
    };

    最后,当删除历史搜索之后,也需要更新滚动条

    修改src/pages/search/index.vue

    修改src/pages/search/history.vue

    (因为页面加载时有100ms延迟的动画,因此这里更新滚动条也需要相同的延迟)

    注意滚动条组件的更新操作,需要使用 $nextTick( ) 实现异步

  • 相关阅读:
    Composer autoload 自动加载
    权限问题
    加载适配器和布局之间的顺序关系--Unsolved
    listview和button
    线程练习中出现的错误
    线程02
    关于初始化成员变量
    可扩展列表
    Android开发中Handler的经典总结----转载
    线程01
  • 原文地址:https://www.cnblogs.com/chenyingying0/p/12670289.html
Copyright © 2011-2022 走看看