vue2.0+webpack高仿餓了麼商家頁面

vue2.0+webpack高仿餓了麼商家頁面

一、項目說明

1. 項目技術

  • vue cli
  • vue
  • ES6
  • vue-resource
  • vue-router
  • better-scroll
  • sass
  • webpack
  • git

2. 項目地址:Github

3. 項目演示

  • sell
  • 手機掃描二維碼

    手機掃描二維碼

二、項目初始化

1. 使用vue官網給出的命令行工具
# 全局安裝 vue-cli
$ npm install --global vue-cli
# 創建一個基於 webpack 模板的新項目
$ vue init webpack my-project(sell)
# 安裝依賴,走你
$ cd my-project(sell)
$ npm install
$ npm run dev
2. 初始項目目錄(小部分修改)
├── build              // 構建服務和webpack配置
├── config             // 項目不同環境的配置
├── index.html         // 項目入口文件
├── package.json       // 項目配置文件
├── src                // 生產目錄
│   ├── common        // 公共的css js 資源
│   ├── components    // 各種組件
│   ├── router        // 路由
│   ├── App.vue       // 主頁面 
│   └── main.js       // Webpack 預編譯入口
3. 完整項目目錄

接下來開始項目的開發

1. webpack 預編譯入口文件 main.js

import Vue from 'vue';
import App from './App';
import router from './router';
// index.js會自動尋找到,也可以寫全(import router from './router/index.js'import VueResource from 'vue-resource';
import './common/styles/index.scss';

/* 設置爲 false 以阻止 vue 在啓動時生成生產提示 */
Vue.config.productionTip = false;

Vue.use(VueResource); // 全局註冊

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
});

2. 主頁面 App.vue

(1)先上代碼
<template>
  <div id="app">
    <v-header v-bind:seller="seller"></v-header>
    <div class='tab border-b-1px'>
      <div class='item-tab'>
        <router-link to="/goods">商品</router-link>
      </div>
      <div class='item-tab'>
        <router-link to="/ratings">評論</router-link>
      </div>
      <div class='item-tab'>
         <router-link to="/seller">商家</router-link>
      </div>
    </div>
    <!--主要用於保留組件狀態或避免重新渲染-->
    <keep-alive>
        <router-view v-bind:seller="seller"></router-view>
    </keep-alive>
  </div>
</template>

<script>
import header from './components/header/header.vue';
import {urlParse} from './common/js/util.js';
// import data from './common/json/data.json';
const ERR_OK=0;
export default {
  name:'App',
  data () {
    return {
        seller:{
            id: (() => {
                let queryParam=urlParse();
                return queryParam.id;
            })()
        }
    };
  },
  created (){
        this.$http.get('/api/seller?id=' + this.seller.id).then(response => {
            if(response.data.errno===ERR_OK){

// 用於對象的合併 , Object.assign方法的第一個參數是目標對象,後面的參數都是源對象。
                this.seller=Object.assign({},this.seller,response.data.data);
            }else{
                console.log('no data');
            }
        });
    // this.seller=Object.assign({},this.seller,data.seller);
  },
  components:{
    'v-header':header
  }
};
</script>

<style lang="scss" scoped>
  @import "./common/styles/mixin.scss";
  #app{
     .tab{
        display:flex;
        width:100%;
        height:40px;
        line-height:40px;
        font-size:14px;
        color:rgb(240,20,20);
        @include border-b-1px(rgba(7,17,27,0.1));
        .item-tab{
            flex:1;
            text-align:center;
            .active{
                color:rgb(240,20,20);
            }
        }
    }
  }
</style>

(2)代碼關鍵說明

主頁面的結構

  • v-header 組件
  • 路由渲染組件

功能實現

1) 獲取mock數據
  • 在入口文件中main.js 中, 引入 vue-resource ,並全局註冊
import VueResource from 'vue-resource';

Vue.use(VueResource); // 全局註冊
  • App.vue 中 ,使用生命週期鉤子 created , 在實例創建完成後被立即調用,請求數據。且需要在data 選項中返回數據對象(其中的id 放在後面介紹)。
data () {
    return {
        seller:{
            id: (() => {
                let queryParam=urlParse();
                return queryParam.id;
            })()
        }
    };
  },
created (){
        this.$http.get('/api/seller?id=' + this.seller.id).then(response => {
            if(response.data.errno===ERR_OK){
                // 用於對象的合併 , Object.assign方法的第一個參數是目標對象,後面的參數都是源對象。
                this.seller=Object.assign({},this.seller,response.data.data);
            }else{
                console.log('no data');
            }
        });
    // this.seller=Object.assign({},this.seller,data.seller);
  },

當一個組件被定義,data 必須聲明爲返回一個初始數據對象的函數,因爲組件可能被用來創建多個實例。如果 data 仍然是一個純粹的對象,則所有的實例將共享引用同一個數據對象!通過提供 data 函數,每次創建一個新實例後,我們能夠調用 data 函數,從而返回初始數據的一個全新副本數據對象。

3) 在父組件中使用子組件
  • 首先,引入子組件
import header from './components/header/header.vue';
  • 然後 ,父組件 通過 components 選項聲明
components:{
    'v-header':header
}
  • 使用很簡單
<v-header v-bind:seller="seller"></v-header>
4) 父子組件的數據傳遞
  • 父組件 App.vue:通過 v-bind:seller=”seller” 將數據傳遞
  • 子組件 header.vue :通過 props 接收(只顯示核心代碼)
<template>
...
</template>

<script>
export default{
...
  props: {
    seller:{
        type:Object,
        required:true
    }
  }
...
};
</script>

<style lang="scss" scoped>
...
</style>
這樣, 在子組件中就可以使用父組件的數據了
5) 路由
  • 首先我們需要將 路由的定義 單獨放在一個文件中 index.js
import Vue from 'vue';
import Router from 'vue-router';
import goods from '@/components/goods/goods.vue';
import ratings from '@/components/ratings/ratings.vue';
import seller from '@/components/seller/seller.vue';

Vue.use(Router);

const routes=[
    {
      path: '/',
      redirect: 'goods', // 重定向
    },
    {
      path: '/goods',
      component: goods
    },
    {
      path: '/ratings',
      component: ratings
    },
    {
      path: '/seller',
      component: seller
    }
];
export default new Router({
   // 激活class類名
   linkActiveClass:'active',
   routes
});
  • 然後在 main.js 中 引入routerindex.js ,並且傳一個路由屬性給Vue實例 ,實現路由的注入
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
});
  • App.vue 中通過 router-link 組件實現

    <router-link to="/goods">商品</router-link>

    注意:不支持target=”_blank”屬性,如果你想打開一個新標籤頁,你必須用標籤。

  • 最後通過 router-view 組件渲染

<keep-alive>
    <router-view v-bind:seller="seller"></router-view>
</keep-alive>
// keep-alive 主要用於保留組件狀態或避免重新渲染
以上就實現了一個簡單的路由

(3) 第一個組件 header.vue

<template>
  <div class="header">
    <!--內容區-->
    <div class="content-wrapper">
      <div class="avatar">
        <img v-bind:src="seller.avatar" width="64" height='64'/>
      </div>
      <div class="content">
        <div class="title">
            <span class="brand"></span>
            <span class="name">{{seller.name}}</span>
        </div>
        <div class="description">
            {{seller.description}}/{{seller.deliveryTime}}分鐘送達
        </div>
        <div v-if="seller.supports" class="support"><!--判斷是否存在-->
            <span class="icon" v-bind:class="classMap[seller.supports[0].type]"></span>
            <span class="text">{{seller.supports[0].description}}</span>
        </div>
      </div>
      <div v-if="seller.supports" class="support-count" @click="showDetail">
        <span class="count">{{seller.supports.length}}</span>
        <i class="icon-keyboard_arrow_right"></i>
      </div>
    </div>
    <!--公告區-->
    <div class="bulletin-wrapper" @click="showDetail">
        <span class="bulletin-title"></span><span class="bulletin-text">{{seller.bulletin}}</span>
        <i class="icon-keyboard_arrow_right"></i>
    </div>
    <!--背景-->
    <div class="background">
        <img v-bind:src="seller.avatar" width="100%" height="100%" />
    </div>
    <!--遮罩層 ,詳情頁-->
    <transition name="fade">
        <div class="detail" v-show="detailShow">
            <div class="detail-wrapper clearfix">
                <div class="detail-main">
                    <h1 class="name">{{seller.name}}</h1>
                    <div class="star-wrapper">
                        <star :size="48" :score="seller.score"></star>
                    </div>
                    <div class="title">
                        <div class="line"></div>
                        <div class="text">優惠信息</div>
                        <div class="line"></div>
                    </div>
                    <ul v-if="seller.supports" class="supports">
                        <li class="support-item" v-for="(item ,index) in seller.supports">
                          <span class="icon" :class="classMap[seller.supports[index].type]"></span>
                          <span class="text">{{seller.supports[index].description}}</span>
                        </li>
                    </ul>
                    <div class="title">
                        <div class="line"></div>
                        <div class="text">商家公告</div>
                        <div class="line"></div>
                    </div>
                    <div class="bulletin">
                         <p class="content">{{seller.bulletin}}</p>
                    </div>
                </div>
            </div>
            <div class="detail-close" @click="hideDetail">
                <i class="icon-close"></i>
            </div>
        </div>
      </transition>
  </div>
</template>

<script>
import star from "../star/star.vue";
export default{
    data(){
        return{
            classMap:[],
            detailShow:false
        }
    },
    methods:{
        showDetail(){
            this.detailShow=true;
        },
        hideDetail(){
            this.detailShow=false;
        }
    },
  props: {
    seller:{
        type:Object,
        required:true
    }
  },
  created (){
    this.classMap=['decrease','discount','guarantee','invoice','special'];
  },
  components:{
    star
  }
};
</script>

<style lang="scss" scoped>
@import "../../common/styles/mixin.scss";

.header {
    position:relative;
    overflow:hidden;
    color:#fff;
    background:rgba(7,17,27,0.5);
    .content-wrapper {
        position:relative;
        padding:24px 12px 18px 24px;
        font-size:0;
        .avatar {
            display:inline-block;
            vertical-align:top;
            img{
                border-radius:2px;
            }
        }
        .content {
            display:inline-block;
            margin-left:16px;
            font-size:14px;
            .title {
                margin:2px 0 8px 0;
                .brand {
                    display:inline-block;
                    vertical-align:top;
                    width:30px;
                    height:18px;
                    @include bg-image("img/brand");
                    background-size:30px 18px;
                    background-repeat: no-repeat;
                }
                .name {
                    margin-left:6px;
                    font-size:16px;
                    line-height:18px;
                    font-weight: bold;
                }
            }
            .description {
                    margin-bottom: 10px;
                    line-height:12px;
                    font-size:12px;
            }
            .support {
                .icon {
                    display:inline-block;
                    vertical-align:top;
                    width:12px;
                    height:12px;
                    margin-right: 4px;
                    background-size: 12px 12px;
                    background-repeat: no-repeat;
                    &.decrease {
                        @include bg-image("img/decrease_1");
                    }
                    &.discount {
                        @include bg-image("img/discount_1");
                    }
                    &.guarantee {
                        @include bg-image("img/guarantee_1");
                    }
                    &.invoice {
                        @include bg-image("img/invoice_1");
                    }
                    &.special {
                        @include bg-image("img/special_1");
                    }
                }
                .text{
                    line-height:12px;
                    font-size:10px;
                }
            }
        }
        .support-count {
            position:absolute;
            right:12px;
            bottom:14px;
            padding:0 8px;
            height:24px;
            line-height: 24px;
            border-radius:14px;
            background-color:rgba(0,0,0,0.2);
            .count{
                vertical-align:top;
                font-size: 10px;
            }
            .icon-keyboard_arrow_right{
                margin-left: 2px;
                line-height:24px;
                font-size: 10px;
            }
        }
    }
    .bulletin-wrapper{
        position:relative;
        height:28px;
        line-height: 28px;
        padding:0 22px 0 12px;
        white-space: nowrap;
        overflow:hidden;
        text-overflow:ellipsis;
        background:rgba(7,17,27,0.2);
        .bulletin-title{
            display:inline-block;
            vertical-align:top;
            margin-top:8px;
            width:22px;
            height:12px;
            @include bg-image("img/bulletin");
            background-size:22px 12px;
            background-repeat:no-repeat;
        }
        .bulletin-text{
            vertical-align:top;
            margin:0 4px;
            font-size:10px;
        }
        .icon-keyboard_arrow_right{
            position:absolute;
            font-size:10px;
            right:12px;
            top:8px;
        }
    }
    .background{
        position:absolute;
        top:0;
        left:0;
        width:100%;
        height:100%;
        background:red;
        z-index:-1;
        filter:blur(10px);
    }
    .detail{
        position:fixed;
        z-index:100;
        top:0;
        left:0;
        width:100%;
        height:100%;
        overflow:auto;
        background:rgba(7,17,27,0.8);
        backdrop-filter:blur(10px);
        transition: all 0.5s;
        &.fade-enter,&.fade-leave-to{
          opacity:0;
        }
        .detail-wrapper{
            width:100%;
            min-height: 100%;
            .detail-main{
                margin-top:64px;
              padding-bottom:64px;
              .name{
                line-height:16px;
                text-align:center;
                font-size:16px;
                font-weight: 700;
              }
              .star-wrapper{
                 margin-top:16px;
                 padding:2px 0;
                 text-align:center;
              }
              .title{
                display:flex;
                width:80%;
                margin:28px auto 24px auto;
                .line{
                    flex:1;
                    position:relative;
                    top:-6px;
                    border-bottom:1px solid rgba(255,255,255,0.2);
                }
                .text{
                    padding:0 12px;
                    font-size:15px;
                    font-weight:700;
                }
              }
              .supports{
                width:80%;
                margin:0 auto;
                .support-item{
                    padding:0 12px;
                    margin-bottom:12px;
                    font-size:0px;
                    &:last-child{
                        margin-bottom:0;
                    }
                    .icon{
                        display:inline-block;
                        vertical-align: top;
                        width:16px;
                        height:16px;
                        margin-right:6px;
                        background-size:16px 16px;
                        background-repeat: no-repeat;
                        &.decrease {
                               @include bg-image("img/decrease_2");
                        }
                        &.discount {
                                @include bg-image("img/discount_2");
                        }
                        &.guarantee {
                                @include bg-image("img/guarantee_2");
                        }
                        &.invoice {
                                @include bg-image("img/invoice_2");
                        }
                        &.special {
                                @include bg-image("img/special_2");
                        }
                    }
                    .text{
                        line-height:16px;
                        font-size:12px;
                    }
                }
              }
              .bulletin{
                width:80%;
                margin:0 auto;
                .content{
                    padding:0 12px;
                    line-height:24px;
                    font-size:12px;
                }
              }
            }
        }
        .detail-close{
            position:relative;
            width:32px;
            height:32px;
            margin:-64px auto 0 auto;
            font-size:32px;
        }
    }
}
</style>
主要知識點
  • 指令

    • v-if :根據表達式的值的真假條件渲染元素
    • v-for :基於源數據多次渲染元素或模板塊
    • v-bind :動態地綁定一個或多個特性,或一個組件 prop 到表達式
    • v-on :綁定事件監聽器
      • 修飾符
      • stop:阻止事件冒泡
      • prevent:阻止默認事件
      • v-on:click.stop="doThis"
  • 過渡動畫 :transition 組件

  • 選項 :methods,created
功能實現
1)動畫 : 利用 transition 組件
  • 在需要實現動畫的容器最外層 包裹 transition 組件,並且指定name 屬性
<transition name="fade">
...
</transition>
  • 在css 中制定樣式
transition: all 0.5s;
&.fade-enter,&.fade-leave-to{
     opacity:0;
}
在進入/離開的過渡中,會有 6 個 class 切換。下面這個圖很清楚的可以看出類名的切換

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章