• vue.js高仿饿了么(前期整理)


    1、熟悉项目开发流程

    需求分析——>脚手架工具——>数据mock——>架构设计——>代码编写——>自测——>编译打包。

    2、熟悉代码规范

    从架构设计、组件抽象、模块拆分,到代码风格统一、CSS代码规范和JavaScript变量命名规范,以标准写代码,开发出扩展性、通用性强的优质代码。

    3、掌握Vue.js在实战中应用

    4、学会使用Vue.js完整地开发移动端App

    5、学会工程化开发、组件化开发和模块化开发的方式

    6、酷炫的交互设计

    所用到的技术

    后端:vue-resource(ajax通信)

    前端:vue-router(官方插件管理路由)、localstorage、flex布局、css sticky footer布局、html、css、es6、Vue-cli(脚手架,用来搭建基本代码框架)、vue(热门前端框架)

    其他:webpack(构建工具)、eslint(代码风格检查工具)

    第三方库:better-scroll

    Vue.js出现的历史原因也是前端的发展趋势

    1、旧浏览器逐渐淘汰,移动端需求增加;

    2、前端交互越来越多,功能越来越复杂;

    3、架构从传统后台MVC向REST API+前端MV*(MVC、MVP、MVVM)迁移。

    MVVM框架具有的优点

    1、针对具有复杂交互逻辑的前端应用;

    2、提供基础的架构抽象;

    3、通过Ajax数据持久化,保证前端用户体验。

    下面这张图充分说明了什么是MVVM框架,也说明vue.js双向数据绑定的基本原理:

     

    什么是vue.js

    它是一个轻量级MVVM框架,主要用于数据驱动+组件化的前端开发。它具有以下特点(数据驱动和组件化为核心思想):

    1、轻量级(大小只有20k+)

    2、简洁(容易上手,学习曲线平稳)

    3、快速

    4、组件化:扩展HTML元素,封装可重用的代码。设计的原则为(1)页面上每个独立的可视/可交互区域视为一个组件;(2)每个组件对应一个工程目录,组件所需要的各种资源在这个目录下就近维护;(3)页面不过是组件的容器,组件可以嵌套自由组合,形成完整的页面。

    5、数据驱动:DOM是数据的一种自然映射,看下面两张图进行理解:

             

    结合上面两张图,再看看下面一张图,进一步理解vue.js实现双向数据绑定的原理(又称为数据响应原理,即数据【model】改变驱动视图【view】自动更新):

    6、模块友好

    Vue-cli介绍

    这是一款Vue的脚手架工具,脚手架的含义就是编写好基础的代码。vue-cli帮助我们编写好了目录结构、本地调试、代码部署、热加载和单元测试。

    github地址:https://github.com/vuejs/vue-cli

    安装Vue-cli

    基本教程戳这里

    【注意】

    初次使用,千万不要启动ESLint语法规范。

    webpack打包

    webpack就是将各种资源打包,然后输出js、图片、css静态文件。

    新姿势

    设备像素比(DPR)

    图标字体的制作

    打开https://icomoon.io/ => 点击右上角icomoon app => 点击左上角import icons =>选择做好的svg文件 => 点击左上角untitled set选择上图标 => 点击右下角generate fonts =>再点击右下角download即可下载(左上角preferences是修改下载文件夹的名称)。

    使用图标字体

    设计好项目目录,将fonts文件夹下的文件复制粘贴进项目中,将style.css复制粘贴进stylus目录中,并改名为icon.styl,同时将里面的内容改为stylus语法

    mock数据

    作为前端经常需要模拟后台数据,我们称之为mock,通常的方式为自己搭建一个服务器或者创建一个json文件,返回我们想要的数据。

     在这一步有个坑,就是vue-cli更新了,很多配置都发生了变更。在这里有很好的解决方法。下面贴上webpack.dev.conf.js更改后的代码

    'use strict'
    const utils = require('./utils')
    const webpack = require('webpack')
    const config = require('../config')
    const merge = require('webpack-merge')
    const path = require('path')
    const baseWebpackConfig = require('./webpack.base.conf')
    const CopyWebpackPlugin = require('copy-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
    const portfinder = require('portfinder')
    
    //首先
    const express = require('express')
    const app = express()
    var appData = require('../data.json')
    var seller = appData.seller
    var goods = appData.goods
    var ratings = appData.ratings
    var apiRoutes = express.Router()
    app.use('/api', apiRoutes)
    
    const HOST = process.env.HOST
    const PORT = process.env.PORT && Number(process.env.PORT)
    
    const devWebpackConfig = merge(baseWebpackConfig, {
      module: {
        rules: utils.styleLoaders({
          sourceMap: config.dev.cssSourceMap,
          usePostCSS: true
        })
      },
      // cheap-module-eval-source-map is faster for development
      devtool: config.dev.devtool,
    
      // these devServer options should be customized in /config/index.js
      devServer: {
        clientLogLevel: 'warning',
        historyApiFallback: {
          rewrites: [{
            from: /.*/,
            to: path.join(config.dev.assetsPublicPath, 'index.html')
          }, ],
        },
        hot: true,
        contentBase: false, // since we use CopyWebpackPlugin.
        compress: true,
        host: HOST || config.dev.host,
        port: PORT || config.dev.port,
        open: config.dev.autoOpenBrowser,
        overlay: config.dev.errorOverlay ? {
          warnings: false,
          errors: true
        } : false,
        publicPath: config.dev.assetsPublicPath,
        proxy: config.dev.proxyTable,
        quiet: true, // necessary for FriendlyErrorsPlugin
        watchOptions: {
          poll: config.dev.poll,
        },
    
        //找到devServer,添加
        before(app) {
          app.get('/api/seller', (req, res) => {
              res.json({
                // 这里是你的json内容
                errno: 0,
                data: seller
              })
            }),
            app.get('/api/goods', (req, res) => {
              res.json({
                // 这里是你的json内容
                errno: 0,
                data: goods
              })
            }),
            app.get('/api/ratings', (req, res) => {
              res.json({
                // 这里是你的json内容
                errno: 0,
                data: ratings
              })
            })
        }
      },
      plugins: [
        new webpack.DefinePlugin({
          'process.env': require('../config/dev.env')
        }),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
        new webpack.NoEmitOnErrorsPlugin(),
        // https://github.com/ampedandwired/html-webpack-plugin
        new HtmlWebpackPlugin({
          filename: 'index.html',
          template: 'index.html',
          inject: true
        }),
        // copy custom static assets
        new CopyWebpackPlugin([{
          from: path.resolve(__dirname, '../static'),
          to: config.dev.assetsSubDirectory,
          ignore: ['.*']
        }])
      ]
    })
    
    module.exports = new Promise((resolve, reject) => {
      portfinder.basePort = process.env.PORT || config.dev.port
      portfinder.getPort((err, port) => {
        if (err) {
          reject(err)
        } else {
          // publish the new Port, necessary for e2e tests
          process.env.PORT = port
          // add port to devServer config
          devWebpackConfig.devServer.port = port
    
          // Add FriendlyErrorsPlugin
          devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
            compilationSuccessInfo: {
              messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
            },
            onErrors: config.dev.notifyOnErrors ?
              utils.createNotifierCallback() : undefined
          }))
    
          resolve(devWebpackConfig)
        }
      })
    })
    View Code

    没有启动ESlint语法检查的webpack.dev.conf.js的代码

    'use strict'
    const utils = require('./utils')
    const webpack = require('webpack')
    const config = require('../config')
    const merge = require('webpack-merge')
    const path = require('path')
    const baseWebpackConfig = require('./webpack.base.conf')
    const CopyWebpackPlugin = require('copy-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
    const portfinder = require('portfinder')
    
    //首先
    const express = require('express')
    const app = express()
    var appData = require('../data.json')
    var seller = appData.seller
    var goods = appData.goods
    var ratings = appData.ratings
    var apiRoutes = express.Router()
    app.use('/api', apiRoutes)
    
    const HOST = process.env.HOST
    const PORT = process.env.PORT && Number(process.env.PORT)
    
    const devWebpackConfig = merge(baseWebpackConfig, {
      module: {
        rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
      },
      // cheap-module-eval-source-map is faster for development
      devtool: config.dev.devtool,
    
      // these devServer options should be customized in /config/index.js
      devServer: {
        clientLogLevel: 'warning',
        historyApiFallback: {
          rewrites: [
            { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
          ],
        },
        hot: true,
        contentBase: false, // since we use CopyWebpackPlugin.
        compress: true,
        host: HOST || config.dev.host,
        port: PORT || config.dev.port,
        open: config.dev.autoOpenBrowser,
        overlay: config.dev.errorOverlay
          ? { warnings: false, errors: true }
          : false,
        publicPath: config.dev.assetsPublicPath,
        proxy: config.dev.proxyTable,
        quiet: true, // necessary for FriendlyErrorsPlugin
        watchOptions: {
          poll: config.dev.poll,
        },
        //找到devServer,添加
        before(app) {
          app.get('/api/seller', (req, res) => {
              res.json({
                // 这里是你的json内容
                errno: 0,
                data: seller
              })
            }),
            app.get('/api/goods', (req, res) => {
              res.json({
                // 这里是你的json内容
                errno: 0,
                data: goods
              })
            }),
            app.get('/api/ratings', (req, res) => {
              res.json({
                // 这里是你的json内容
                errno: 0,
                data: ratings
              })
            })
        }
      },
      plugins: [
        new webpack.DefinePlugin({
          'process.env': require('../config/dev.env')
        }),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
        new webpack.NoEmitOnErrorsPlugin(),
        // https://github.com/ampedandwired/html-webpack-plugin
        new HtmlWebpackPlugin({
          filename: 'index.html',
          template: 'index.html',
          inject: true
        }),
        // copy custom static assets
        new CopyWebpackPlugin([
          {
            from: path.resolve(__dirname, '../static'),
            to: config.dev.assetsSubDirectory,
            ignore: ['.*']
          }
        ])
      ]
    })
    
    module.exports = new Promise((resolve, reject) => {
      portfinder.basePort = process.env.PORT || config.dev.port
      portfinder.getPort((err, port) => {
        if (err) {
          reject(err)
        } else {
          // publish the new Port, necessary for e2e tests
          process.env.PORT = port
          // add port to devServer config
          devWebpackConfig.devServer.port = port
    
          // Add FriendlyErrorsPlugin
          devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
            compilationSuccessInfo: {
              messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
            },
            onErrors: config.dev.notifyOnErrors
            ? utils.createNotifierCallback()
            : undefined
          }))
    
          resolve(devWebpackConfig)
        }
      })
    })
    View Code

    为了更好的查看调取api回来的数据,建议安装Google的jsonview插件。然后在浏览器查看是否成功返回数据,测试:http://localhost:8080/api/goods

    此时在项目中导入了一个data.json文件

     

    组件拆分部分(一) 启动ESlint语法时,对各种格式的检查到了骇人的地步,多个;和换个行都能报warning!并且js文件的最后还要求换行!注释还要遵守规范!!

    步骤一:导入static文件reset.css

    步骤二:删除默认组件HelloWorld.vue和assets文件夹,修改router中的index.js

    import Vue from 'vue'
    import Router from 'vue-router'
    import header from '@/components/header/header'
    
    Vue.use(Router)
    
    export default new Router({
      routes: [
        {
          path: '/',
          name: 'header',
          component: header
        }
      ]
    })
    View Code

    步骤三:分别配置App.vue和main.js

    App.vue
    
    <template>
      <div id="app">
        <v-header></v-header>
        <div class="tab">
          我是导航区块
        </div>
        <div class="conpent">
          我是内容区块
        </div>
      </div>
    </template>
    
    <script>
      import header from './components/header/header.vue'
      export default {
        name: 'app',
        components: {
          'v-header': header
        }
      }
    </script>
    
    <style scoped>
    
    </style>
    View Code
    // 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'
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      template: '<App/>',
      components: { App }
    })
    View Code

    步骤四:增加一个header.vue文件

    <template>
      <div class="header">
        我是header
      </div>
    </template>
    
    <script>
    export default {
      name: 'v-header'
    }
    </script>
    
    <style scoped>
    
    </style>
    View Code

    组件拆分部分(二) 

    由于需要用到stylus语法,因此事先得安装stylus 和 stylus-loader的相关依赖包

    步骤一:修改App.vue文件

    <template>
      <div id="app">
        <v-header></v-header>
        <div class="tab">
          <div class="tab-item">商品</div>
          <div class="tab-item">评论</div>
          <div class="tab-item">商家</div>
        </div>
        <div class="conpent">
          我是内容区块
        </div>
      </div>
    </template>
    
    <script>
      import header from './components/header/header.vue'
      export default {
        name: 'app',
        components: {
          'v-header': header
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
    #app
      .tab
        display: flex
         100%
        height: 40px
        line-height: 40px
        .tab-item
          flex: 1
          text-align: center
    </style>
    View Code

    配置路由规则

    配置路由规则没什么难度,去官方网站瞧一瞧就行了,这里有个linkActiveClass属性,挺有用的。下面稍微看一看配置过程

    步骤一:创建一个goods.vue文件,其他两个ratings.vue和seller.vue类似

    <template>
      <div>
        我是goods
      </div>
    </template>
    
    <script>
    export default {
      name: 'v-goods'
    }
    </script>
    
    <style scoped>
    
    </style>
    View Code

    步骤二:在index.js文件中配置路由规则

    import Vue from 'vue'
    import Router from 'vue-router'
    import goods from '../components/goods/goods'
    import ratings from '../components/ratings/ratings'
    import seller from '../components/seller/seller'
    
    Vue.use(Router)
    
    export default new Router({
    // 改变路由激活时的class名称
      linkActiveClass: 'active',
      routes: [
        {
          path: '/',
          component: goods
        },
        {
          path: '/goods',
          component: goods
        },
        {
          path: '/ratings',
          component: ratings
        },
        {
          path: '/seller',
          component: seller
        }
      ]
    })
    View Code

    另一种路由规则配置

    import Vue from 'vue'
    import Router from 'vue-router'
    import header from '@/components/header/header'
    import goods from '@/components/goods/goods'
    import ratings from '@/components/ratings/ratings'
    import seller from '@/components/seller/seller'
    
    Vue.use(Router)
    
    export default new Router({
      // 改变路由激活时的class名称
      linkActiveClass: 'active',
      routes: [
        {
          path: '/',
          name: 'header',
          component: header
        },
        {
          path: '/goods',
          name: 'goods',
          component: goods
        },
        {
          path: '/ratings',
          name: 'ratings',
          component: ratings
        },
        {
          path: '/seller',
          naem: 'seller',
          component: seller
        }
      ]
    })
    View Code

    步骤三:配置根组件App.vue

    <template>
      <div>
        <v-header></v-header>
        <div class="tab">
          <div class="tab-item">
              <router-link to="/goods">商品</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/ratings">评价</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/seller">商家</router-link>
          </div>
        </div>
        <router-view></router-view>
      </div>
    </template>
    
    <script>
      import header from './components/header/header.vue'
      export default {
        name: 'app',
        components: {
          'v-header': header
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .tab
        display: flex
         100%
        height: 40px
        line-height: 40px
        .tab-item
          flex: 1
          text-align: center
          & > a
           display:block
           font-size:16px
           color:rgb(77,85,93)
           &.active
            color:rgb(240,20,20)
    </style>
    View Code

    在局域网内,通过手机访问webapp项目

    命令行:ipconfig查询本机IP => 用IP代替localhost => 将地址复制到草料二维码的官方网站生成二维码 => 用微信扫一扫(假如微信扫不了的话,下载一个二维码扫描来扫描)

    【注意】

    需要解决ip地址无法访问vue项目的问题

    1、将config文件夹中的index.js文件修改

    'use strict'
    // Template version: 1.3.1
    // see http://vuejs-templates.github.io/webpack for documentation.
    
    const path = require('path')
    
    module.exports = {
      dev: {
    
        // Paths
        assetsSubDirectory: 'static',
        assetsPublicPath: '/',
        proxyTable: {},
    
        // Various Dev Server settings
        host: '0.0.0.0', // can be overwritten by process.env.HOST
        port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
        autoOpenBrowser: false,
        errorOverlay: true,
        notifyOnErrors: true,
        poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
    
        
        /**
         * Source Maps
         */
    
        // https://webpack.js.org/configuration/devtool/#development
        devtool: 'cheap-module-eval-source-map',
    
        // If you have problems debugging vue-files in devtools,
        // set this to false - it *may* help
        // https://vue-loader.vuejs.org/en/options.html#cachebusting
        cacheBusting: true,
    
        cssSourceMap: true
      },
    
      build: {
        // Template for index.html
        index: path.resolve(__dirname, '../dist/index.html'),
    
        // Paths
        assetsRoot: path.resolve(__dirname, '../dist'),
        assetsSubDirectory: 'static',
        assetsPublicPath: '/',
    
        /**
         * Source Maps
         */
    
        productionSourceMap: true,
        // https://webpack.js.org/configuration/devtool/#production
        devtool: '#source-map',
    
        // Gzip off by default as many popular static hosts such as
        // Surge or Netlify already gzip all static assets for you.
        // Before setting to `true`, make sure to:
        // npm install --save-dev compression-webpack-plugin
        productionGzip: false,
        productionGzipExtensions: ['js', 'css'],
    
        // Run the build command with an extra argument to
        // View the bundle analyzer report after build finishes:
        // `npm run build --report`
        // Set to `true` or `false` to always turn it on or off
        bundleAnalyzerReport: process.env.npm_config_report
      }
    }
    View Code

    2、重启服务

    由于DPR引发的1px问题以及解决方式

    步骤一:创建一个mixin.styl文件,作为边框的公共样式

    border-1px($color)
      position: relative
      &:after
        display: block
        position: absolute
        left: 0
        bottom: 0
         100%
        border-top: 1px solid $color
        content: ' '
    View Code

    步骤二:创建一个base.styl文件,解决1px问题

    @media (-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5)
      .border-1px
        &::after
          -webkit-transform: scaleY(0.7)
          transform: scaleY(0.7)
    
    @media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2)
      .border-1px
        &::after
          -webkit-transform: scaleY(0.5)
          transform: scaleY(0.5)
    View Code

    步骤三:创建一个index.styl文件,统一导入styl文件

    @import "./mixin"
    @import "./icon"
    @import "./base"
    View Code

    步骤四:修改icon.styl文件,需要更改url路径,不然会报错

    @font-face
      font-family: 'sell-icon'
      src:  url('../fonts/sell-icon.eot?2430tu')
      src:  url('../fonts/sell-icon.eot?2430tu#iefix') format('embedded-opentype'),
        url('../fonts/sell-icon.ttf?2430tu') format('truetype'),
        url('../fonts/sell-icon.woff?2430tu') format('woff'),
        url('../fonts/sell-icon.svg?2430tu#sell-icon') format('svg')
      font-weight: normal
      font-style: normal
    
    
    [class^="icon-"], [class*=" icon-"]
      /* use !important to prevent issues with browser extensions that change fonts */
      font-family: 'sell-icon' !important
      speak: none
      font-style: normal
      font-weight: normal
      font-variant: normal
      text-transform: none
      line-height: 1
    
      /* Better Font Rendering =========== */
      -webkit-font-smoothing: antialiased
      -moz-osx-font-smoothing: grayscale
    
    
    .icon-add_circle:before
      content: "e900"
    
    .icon-arrow_lift:before
      content: "e901"
    
    .icon-check_circle:before
      content: "e902"
    
    .icon-close:before
      content: "e903"
    
    .icon-favorite:before
      content: "e904"
    
    .icon-keyboard_arrow_right:before
      content: "e905"
    
    .icon-remove_circle_outline:before
      content: "e906"
    
    .icon-shopping_cart:before
      content: "e907"
    
    .icon-thumb_down:before
      content: "e908"
    
    .icon-thumb_up:before
      content: "e909"
    
    .icon-office:before
      content: "e90a"
    View Code

    步骤五:配置router文件夹下的index.js文件

    import Vue from 'vue'
    import Router from 'vue-router'
    import header from '@/components/header/header'
    import goods from '@/components/goods/goods'
    import ratings from '@/components/ratings/ratings'
    import seller from '@/components/seller/seller'
    
    import '@/common/stylus/index.styl'
    
    Vue.use(Router)
    
    export default new Router({
        // 改变路由激活时的class名称
      linkActiveClass: 'active',
      routes: [
        {
          path: '/',
          name: 'header',
          component: header
        },
        {
          path: '/goods',
          name: 'goods',
          component: goods
        },
        {
          path: '/ratings',
          name: 'ratings',
          component: ratings
        },
        {
          path: '/seller',
          naem: 'seller',
          component: seller
        }
      ]
    })
    View Code

    步骤六:在App.vue使用写好的styl样式

    <template>
      <div>
        <v-header></v-header>
        <div class="tab border-1px">
          <div class="tab-item">
              <router-link to="/goods">商品</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/ratings">评价</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/seller">商家</router-link>
          </div>
        </div>
        <router-view></router-view>
      </div>
    </template>
    
    <script>
      import header from './components/header/header.vue'
      export default {
        name: 'app',
        components: {
          'v-header': header
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "./common/stylus/mixin.styl";
      .tab
        display: flex
         100%
        height: 40px
        line-height: 40px
        /*border-bottom: 1px solid rgba(7, 17, 27, 0.1)*/
        border-1px(rgba(7, 17, 27, 0.1))
        .tab-item
          flex: 1
          text-align: center
          & > a
           display:block
           font-size:16px
           color:rgb(77,85,93)
           &.active
            color:rgb(240,20,20)
    </style>
    View Code

    编辑.eslintrc.js的规则

    假如在创建项目中出现如下情况

    那么就打开.eslintrc.js文件,将其规则忽略掉(设置为0)

    清除掉令人抓狂的eslintrc语法规则!

    'use strict'
    const path = require('path')
    const utils = require('./utils')
    const config = require('../config')
    const vueLoaderConfig = require('./vue-loader.conf')
    
    function resolve (dir) {
      return path.join(__dirname, '..', dir)
    }
    
    //const createLintingRule = () => ({
    ////test: /.(js|vue)$/,
    ////loader: 'eslint-loader',
    ////enforce: 'pre',
    ////include: [resolve('src'), resolve('test')],
    ////options: {
    ////  formatter: require('eslint-friendly-formatter'),
    ////  emitWarning: !config.dev.showEslintErrorsInOverlay
    ////}
    //})
    
    module.exports = {
      context: path.resolve(__dirname, '../'),
      entry: {
        app: './src/main.js'
      },
      output: {
        path: config.build.assetsRoot,
        filename: '[name].js',
        publicPath: process.env.NODE_ENV === 'production'
          ? config.build.assetsPublicPath
          : config.dev.assetsPublicPath
      },
      resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
          'vue$': 'vue/dist/vue.esm.js',
          '@': resolve('src'),
        }
      },
      module: {
        rules: [
    //    ...(config.dev.useEslint ? [createLintingRule()] : []),
          {
            test: /.vue$/,
            loader: 'vue-loader',
            options: vueLoaderConfig
          },
          {
            test: /.js$/,
            loader: 'babel-loader',
            include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
          },
          {
            test: /.(png|jpe?g|gif|svg)(?.*)?$/,
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: utils.assetsPath('img/[name].[hash:7].[ext]')
            }
          },
          {
            test: /.(mp4|webm|ogg|mp3|wav|flac|aac)(?.*)?$/,
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: utils.assetsPath('media/[name].[hash:7].[ext]')
            }
          },
          {
            test: /.(woff2?|eot|ttf|otf)(?.*)?$/,
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
            }
          }
        ]
      },
      node: {
        // prevent webpack from injecting useless setImmediate polyfill because Vue
        // source contains it (although only uses it if it's native).
        setImmediate: false,
        // prevent webpack from injecting mocks to Node native modules
        // that does not make sense for the client
        dgram: 'empty',
        fs: 'empty',
        net: 'empty',
        tls: 'empty',
        child_process: 'empty'
      }
    }
    View Code

    使用vue-resource

    步骤一:安装cnpm install vue-resource --save

    步骤二:配置router文件夹中的index.js文件

    import Vue from 'vue'
    import Router from 'vue-router'
    import Resource from 'vue-resource'
    import header from '@/components/header/header'
    import goods from '@/components/goods/goods'
    import ratings from '@/components/ratings/ratings'
    import seller from '@/components/seller/seller'
    
    import '@/common/stylus/index.styl'
    
    Vue.use(Router)
    Vue.use(Resource)
    
    export default new Router({
        // 改变路由激活时的class名称
      linkActiveClass: 'active',
      routes: [
        {
          path: '/',
          name: 'header',
          component: header
        },
        {
          path: '/goods',
          name: 'goods',
          component: goods
        },
        {
          path: '/ratings',
          name: 'ratings',
          component: ratings
        },
        {
          path: '/seller',
          naem: 'seller',
          component: seller
        }
      ]
    })
    View Code

    步骤三:配置App.vue文件

    <template>
      <div>
        <v-header></v-header>
        <div class="tab border-1px">
          <div class="tab-item">
              <router-link to="/goods">商品</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/ratings">评价</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/seller">商家</router-link>
          </div>
        </div>
        <router-view></router-view>
      </div>
    </template>
    
    <script>
      import header from './components/header/header.vue';
      const ERR_OK = 0;
      export default {
        name: 'app',
        data() {
            return {
                seller: {}
            }
        },
        created() {
            this.$http.get('api/seller').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.seller = response.data;
              }
              console.log(response.data);
            })
        },
        components: {
          'v-header': header
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "./common/stylus/mixin.styl";
      .tab
        display: flex
         100%
        height: 40px
        line-height: 40px
        /*border-bottom: 1px solid rgba(7, 17, 27, 0.1)*/
        border-1px(rgba(7, 17, 27, 0.1))
        .tab-item
          flex: 1
          text-align: center
          & > a
           display:block
           font-size:16px
           color:rgb(77,85,93)
           &.active
            color:rgb(240,20,20)
    </style>
    View Code

    切记

    导入stylus样式表必须在style作如下的定义

    <style scoped lang="stylus" rel="stylesheet/stylus"></style> 

    外部组件(一)

    步骤一:修改App.vue,将seller对象的数据传递给组件header.vue

    <template>
      <div>
        <v-header :seller="seller"></v-header>
        <div class="tab border-1px">
          <div class="tab-item">
              <router-link to="/goods">商品</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/ratings">评价</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/seller">商家</router-link>
          </div>
        </div>
        <router-view></router-view>
      </div>
    </template>
    
    <script>
      import header from './components/header/header.vue';
      const ERR_OK = 0;
      export default {
        name: 'app',
        data() {
            return {
                seller: {}
            }
        },
        created() {
            this.$http.get('api/seller').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.seller = response.data;
              }
              console.log(response.data);
              this.seller = response.data;
            })
        },
        components: {
          'v-header': header
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "./common/stylus/mixin.styl";
      .tab
        display: flex
         100%
        height: 40px
        line-height: 40px
        /*border-bottom: 1px solid rgba(7, 17, 27, 0.1)*/
        border-1px(rgba(7, 17, 27, 0.1))
        .tab-item
          flex: 1
          text-align: center
          & > a
           display:block
           font-size:16px
           color:rgb(77,85,93)
           &.active
            color:rgb(240,20,20)
    </style>
    View Code

    步骤二:编写header.vue组件

    <template>
      <div class="header">
        <div class="content-wrapper">
            <div class="avatar">
                <img width="64" height="64" :src="seller.avatar">
            </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"></span>
                    <span class="text">{{seller.supports[0].description}}</span>
                </div>
            </div>
        </div>
        <div class="bulletin-wrapper"></div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'v-header',
      props: {
          seller: {
              type: Object
          }
      }
    }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        .header
            color: #fff
            background: #000
            .content-wrapper
                padding: 24px 12px 18px 24px
                .avatar
                    display: inline-block
    </style>
    View Code

    外部组件(二)

    步骤一:将所需要的图片导入到header文件夹中

    步骤二:编写mixin.styl文件,目的是让程序在不同设备下显示不同的图片大小

    border-1px($color)
      position: relative
      &:after
        display: block
        position: absolute
        left: 0
        bottom: 0
         100%
        border-top: 1px solid $color
        content: ' '
    
    bg-image($url)
      background-image: url($url + "@2x.png")
      @media (-webkit-min-device-pixel-ratio: 3),(min-device-pixel-ratio: 3)
        background-image: url($url + "@3x.png")
    View Code

    步骤三:编写base.styl文件,目的是统一页面字体

    body, html
      line-height: 1
      font-weight: 200
      font-family: 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', arial, sans-serif
    
    @media (-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5)
      .border-1px
        &::after
          -webkit-transform: scaleY(0.7)
          transform: scaleY(0.7)
    
    @media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2)
      .border-1px
        &::after
          -webkit-transform: scaleY(0.5)
          transform: scaleY(0.5)
    View Code

    步骤四:在header.vue文件中增加样式

    <template>
      <div class="header">
        <div class="content-wrapper">
            <div class="avatar">
                <img width="64" height="64" :src="seller.avatar">
            </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"></span>
                    <span class="text">{{seller.supports[0].description}}</span>
                </div>
            </div>
        </div>
        <div class="bulletin-wrapper"></div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'v-header',
      props: {
          seller: {
              type: Object
          }
      }
    }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .header
            color: #fff
            background: #000
            .content-wrapper
                padding: 24px 12px 18px 24px
                font-size: 0
                .avatar
                    display: inline-block
                .content
                    display: inline-block
                    margin-left: 16px
                    font-size: 14px
                    .title
                        margin: 2px 0 8px 0
                        .brand
                            display: inline-block
                            vertical-align: top
                             30px
                            height:18px
                            bg-image("brand")
                            background-size: 30px 18px
                            background-repeat: no-repeat
                        .name
                            margin-left: 6px
                            font-size: 16px
                            line-height: 18px
                            font-weight: bold
    </style>
    View Code

    外部组件(三)

     继续编写header.vue文件,当然相关的图片也要导入进来。我们可以通过修改<span class="icon" :class="classMap[seller.supports[0].type]"></span>的数值来决定要显示的图片,对内容显示操作也是如此:

    <span class="text">{{seller.supports[0].description}}</span>

    <template>
    <div class="header">
      <div class="content-wrapper">
        <div class="avatar">
          <img width="64" height="64" :src="seller.avatar">
        </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" :class="classMap[seller.supports[0].type]"></span>
            <span class="text">{{seller.supports[0].description}}</span>
          </div>
        </div>
      </div>
      <div class="bulletin-wrapper"></div>
    </div>
    </template>
    
    <script>
    export default {
      name: 'v-header',
      props: {
          seller: {
              type: Object
          }
      },
      created() {
          this.classMap = ['decrease','discount','special','invoice','guarantee'];
      }
    }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .header
            color: #fff
            background: #000
            .content-wrapper
                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
                    .title
                        margin: 2px 0 8px 0
                        .brand
                            display: inline-block
                            vertical-align: top
                             30px
                            height:18px
                            bg-image("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
                            height: 12px
                             12px
                            margin-right: 4px
                            background-size: 12px 12px
                            background-repeat: no-repeat
                            &.decrease
                                bg-image('decrease_1')
                            &.discount
                                bg-image('discount_1')
                            &.guarantee
                                bg-image('guarantee_1')
                            &.invoice
                                bg-image('invoice_1')
                            &.special
                                bg-image('special_1')
                        .text
                            font-size: 10px
                            line-height: 12px
    </style>
    View Code

    外部组件(四)

    继续编写header.vue文件

    <template>
    <div class="header">
      <div class="content-wrapper">
        <div class="avatar">
          <img width="64" height="64" :src="seller.avatar">
        </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" :class="classMap[seller.supports[0].type]"></span>
            <span class="text">{{seller.supports[0].description}}</span>
          </div>
        </div>
        <div class="support-count" v-if="seller.supports">
            <span class="count">{{seller.supports.length}}</span>
            <i class="icon-keyboard_arrow_right"></i>
        </div>
      </div>
      <div class="bulletin-wrapper"></div>
    </div>
    </template>
    
    <script>
    export default {
      name: 'v-header',
      props: {
          seller: {
              type: Object
          }
      },
      created() {
          this.classMap = ['decrease','discount','special','invoice','guarantee'];
      }
    }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .header
            color: #fff
            background: #000
            .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
                    .title
                        margin: 2px 0 8px 0
                        .brand
                            display: inline-block
                            vertical-align: top
                             30px
                            height:18px
                            bg-image("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
                            height: 12px
                             12px
                            margin-right: 4px
                            background-size: 12px 12px
                            background-repeat: no-repeat
                            &.decrease
                                bg-image('decrease_1')
                            &.discount
                                bg-image('discount_1')
                            &.guarantee
                                bg-image('guarantee_1')
                            &.invoice
                                bg-image('invoice_1')
                            &.special
                                bg-image('special_1')
                        .text
                            font-size: 10px
                            line-height: 12px
                .support-count
                    position: absolute
                    right: 12px
                    bottom: 14px
                    padding: 0 8px
                    height: 24px
                    line-height: 24px
                    border-radius: 14px
                    background: rgba(0, 0, 0, 0.2)
                    text-align: center
                    .count
                        vertical-align: top
                        font-size: 10px
                    .icon-keyboard_arrow_right
                        margin-left: 2px
                        line-height: 24px
                        font-size: 10px
    </style>
    View Code

    外部组件(五)

    继续编写header.vue文件,增加公告部分

    <template>
    <div class="header">
      <div class="content-wrapper">
        <div class="avatar">
          <img width="64" height="64" :src="seller.avatar">
        </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" :class="classMap[seller.supports[0].type]"></span>
            <span class="text">{{seller.supports[0].description}}</span>
          </div>
        </div>
        <div class="support-count" v-if="seller.supports">
          <span class="count">{{seller.supports.length}}</span>
          <i class="icon-keyboard_arrow_right"></i>
        </div>
      </div>
      <div class="bulletin-wrapper">
        <span class="bulletin-title"></span>
        <span class="bulletin-text">{{seller.bulletin}}</span>
        <i class="icon-keyboard_arrow_right"></i>
      </div>
    </div>
    </template>
    
    <script>
    export default {
      name: 'v-header',
      props: {
          seller: {
              type: Object
          }
      },
      created() {
          this.classMap = ['decrease','discount','special','invoice','guarantee'];
      }
    }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .header
            color: #fff
            background: #000
            .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
                    .title
                        margin: 2px 0 8px 0
                        .brand
                            display: inline-block
                            vertical-align: top
                             30px
                            height:18px
                            bg-image("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
                            height: 12px
                             12px
                            margin-right: 4px
                            background-size: 12px 12px
                            background-repeat: no-repeat
                            &.decrease
                                bg-image('decrease_1')
                            &.discount
                                bg-image('discount_1')
                            &.guarantee
                                bg-image('guarantee_1')
                            &.invoice
                                bg-image('invoice_1')
                            &.special
                                bg-image('special_1')
                        .text
                            font-size: 10px
                            line-height: 12px
                .support-count
                    position: absolute
                    right: 12px
                    bottom: 14px
                    padding: 0 8px
                    height: 24px
                    line-height: 24px
                    border-radius: 14px
                    background: rgba(0, 0, 0, 0.2)
                    text-align: center
                    .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
                     22px
                    height: 12px
                    bg-image('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
    </style>
    View Code

    外部组件(六)

     继续编写header.vue文件,增加顶部蒙层效果

    <template>
    <div class="header">
      <div class="content-wrapper">
        <div class="avatar">
          <img width="64" height="64" :src="seller.avatar">
        </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" :class="classMap[seller.supports[0].type]"></span>
            <span class="text">{{seller.supports[0].description}}</span>
          </div>
        </div>
        <div class="support-count" v-if="seller.supports">
          <span class="count">{{seller.supports.length}}</span>
          <i class="icon-keyboard_arrow_right"></i>
        </div>
      </div>
      <div class="bulletin-wrapper">
        <span class="bulletin-title"></span>
        <span class="bulletin-text">{{seller.bulletin}}</span>
        <i class="icon-keyboard_arrow_right"></i>
      </div>
      <div class="background">
        <img :src="seller.avatar" width="100%" height="100%">
      </div>
    </div>
    </template>
    
    <script>
    export default {
      name: 'v-header',
      props: {
          seller: {
              type: Object
          }
      },
      created() {
          this.classMap = ['decrease','discount','special','invoice','guarantee'];
      }
    }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .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
                    .title
                        margin: 2px 0 8px 0
                        .brand
                            display: inline-block
                            vertical-align: top
                             30px
                            height:18px
                            bg-image("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
                            height: 12px
                             12px
                            margin-right: 4px
                            background-size: 12px 12px
                            background-repeat: no-repeat
                            &.decrease
                                bg-image('decrease_1')
                            &.discount
                                bg-image('discount_1')
                            &.guarantee
                                bg-image('guarantee_1')
                            &.invoice
                                bg-image('invoice_1')
                            &.special
                                bg-image('special_1')
                        .text
                            font-size: 10px
                            line-height: 12px
                .support-count
                    position: absolute
                    right: 12px
                    bottom: 14px
                    padding: 0 8px
                    height: 24px
                    line-height: 24px
                    border-radius: 14px
                    background: rgba(0, 0, 0, 0.2)
                    text-align: center
                    .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
                     22px
                    height: 12px
                    bg-image('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
                 100%
                height: 100%
                z-index: -1
                /*增加模糊度*/
                filter: blur(5px)
    </style>
    View Code

    详情弹层页(一)

     继续编写header.vue文件,增加详情弹层效果

    <template>
    <div class="header">
      <div class="content-wrapper">
        <div class="avatar">
          <img width="64" height="64" :src="seller.avatar">
        </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" :class="classMap[seller.supports[0].type]"></span>
            <span class="text">{{seller.supports[0].description}}</span>
          </div>
        </div>
        <div class="support-count" v-if="seller.supports" @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 :src="seller.avatar" width="100%" height="100%">
      </div>
      <div class="detail" v-show="detailShow"></div>
    </div>
    </template>
    
    <script>
    export default {
      name: 'v-header',
      props: {
          seller: {
              type: Object
          }
      },
      data() {
          return {
              detailShow: false
          };
      },
      methods: {
          showDetail() {
              this.detailShow = true;
          }
      },
      created() {
          this.classMap = ['decrease','discount','special','invoice','guarantee'];
      }
    }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .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
                    .title
                        margin: 2px 0 8px 0
                        .brand
                            display: inline-block
                            vertical-align: top
                             30px
                            height:18px
                            bg-image("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
                            height: 12px
                             12px
                            margin-right: 4px
                            background-size: 12px 12px
                            background-repeat: no-repeat
                            &.decrease
                                bg-image('decrease_1')
                            &.discount
                                bg-image('discount_1')
                            &.guarantee
                                bg-image('guarantee_1')
                            &.invoice
                                bg-image('invoice_1')
                            &.special
                                bg-image('special_1')
                        .text
                            font-size: 10px
                            line-height: 12px
                .support-count
                    position: absolute
                    right: 12px
                    bottom: 14px
                    padding: 0 8px
                    height: 24px
                    line-height: 24px
                    border-radius: 14px
                    background: rgba(0, 0, 0, 0.2)
                    text-align: center
                    .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
                     22px
                    height: 12px
                    bg-image('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
                 100%
                height: 100%
                z-index: -1
                /*增加模糊度*/
                filter: blur(5px)
            .detail
                position: fixed
                top: 0
                left: 0
                z-index: 100
                height: 100%
                 100%
                overflow: auto
                backdrop-filter: blur(10px)
                opacity: 1
                background: rgba(7, 17, 27, 0.8)
    </style>
    View Code

    详情弹层页(二)

    sticky footers布局相关知识

     继续编写header.vue文件,设置弹窗关闭按钮,利用sticky footers知识

    <template>
    <div class="header">
      <div class="content-wrapper">
        <div class="avatar">
          <img width="64" height="64" :src="seller.avatar">
        </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" :class="classMap[seller.supports[0].type]"></span>
            <span class="text">{{seller.supports[0].description}}</span>
          </div>
        </div>
        <div class="support-count" v-if="seller.supports" @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 :src="seller.avatar" width="100%" height="100%">
      </div>
      
      <div class="detail" v-show="detailShow">
          <div class="detail-wrapper clearfix">
              <div class="detail-main"></div>
          </div>
          <div class="detail-close">
              <i class="icon-close"></i>
          </div>
      </div>
    </div>
    </template>
    
    <script>
    export default {
      name: 'v-header',
      props: {
          seller: {
              type: Object
          }
      },
      data() {
          return {
              detailShow: false
          };
      },
      methods: {
          showDetail() {
              this.detailShow = true;
          }
      },
      created() {
          this.classMap = ['decrease','discount','special','invoice','guarantee'];
      }
    }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .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
                    .title
                        margin: 2px 0 8px 0
                        .brand
                            display: inline-block
                            vertical-align: top
                             30px
                            height:18px
                            bg-image("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
                            height: 12px
                             12px
                            margin-right: 4px
                            background-size: 12px 12px
                            background-repeat: no-repeat
                            &.decrease
                                bg-image('decrease_1')
                            &.discount
                                bg-image('discount_1')
                            &.guarantee
                                bg-image('guarantee_1')
                            &.invoice
                                bg-image('invoice_1')
                            &.special
                                bg-image('special_1')
                        .text
                            font-size: 10px
                            line-height: 12px
                .support-count
                    position: absolute
                    right: 12px
                    bottom: 14px
                    padding: 0 8px
                    height: 24px
                    line-height: 24px
                    border-radius: 14px
                    background: rgba(0, 0, 0, 0.2)
                    text-align: center
                    .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
                     22px
                    height: 12px
                    bg-image('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
                 100%
                height: 100%
                z-index: -1
                /*增加模糊度*/
                filter: blur(5px)
            .detail
                position: fixed
                top: 0
                left: 0
                z-index: 100
                height: 100%
                 100%
                overflow: auto
                background: rgba(7, 17, 27, 0.8)
                .detail-wrapper
                    min-height: 100%
                     100%
                    .detail-main
                        margin-top: 64px
                        padding-bottom: 64px
                .detail-close
                    position: relative
                     32px
                    height: 32px
                    margin: -64px auto 0 auto
                    clear: both
                    font-size: 32px
    </style>
    View Code

    编写base.styl文件,设置清除浮动样式

    body, html
      line-height: 1
      font-weight: 200
      font-family: 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', arial, sans-serif
    
    .clearfix
      display: inline-block
      &:after
        display: block
        content: "."
        height: 0
        line-height: 0
        clear: both
        visibility: hidden
    
    @media (-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5)
      .border-1px
        &::after
          -webkit-transform: scaleY(0.7)
          transform: scaleY(0.7)
    
    @media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2)
      .border-1px
        &::after
          -webkit-transform: scaleY(0.5)
          transform: scaleY(0.5)
    View Code

    详情弹层页(三、四)

    步骤一:新建一个通用的组件star.vue

    <template>
      <div class="star" :class="starType">
        <span v-for="itemClass in itemClasses" :class="itemClass" class="star-item"></span>
      </div>
    </template>
    
    <script>
      const LENGTH = 5;
      const CLS_ON = 'on';
      const CLS_HALF = 'half';
      const CLS_OFF = 'off';
    
      export default {
        props: {
          size: {
              type: Number
          },
          score: {
              type: Number
          }
        },
        computed: {
          starType() {
            return 'star-' + this.size;
          },
          itemClasses() {
            let result = [];
            let score = Math.floor(this.score * 2) / 2;
            let hasDecimal = score % 1 !== 0;
            let integer = Math.floor(score);
            for (let i = 0; i < integer; i++) {
              result.push(CLS_ON);
            }
            if (hasDecimal) {
              result.push(CLS_HALF);
            }
            while (result.length < LENGTH) {
              result.push(CLS_OFF);
            }
            return result;
          }
        }
      };
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"
    
      .star
        font-size: 0
        .star-item
          display: inline-block
          background-repeat: no-repeat
        &.star-48
          .star-item
             20px
            height: 20px
            margin-right: 22px
            background-size: 20px 20px
            &:last-child
              margin-right: 0
            &.on
              bg-image('star48_on')
            &.half
              bg-image('star48_half')
            &.off
              bg-image('star48_off')
        &.star-36
          .star-item
             15px
            height: 15px
            margin-right: 6px
            background-size: 15px 15px
            &:last-child
              margin-right: 0
            &.on
              bg-image('star36_on')
            &.half
              bg-image('star36_half')
            &.off
              bg-image('star36_off')
        &.star-24
          .star-item
             10px
            height: 10px
            margin-right: 3px
            background-size: 10px 10px
            &:last-child
              margin-right: 0
            &.on
              bg-image('star24_on')
            &.half
              bg-image('star24_half')
            &.off
              bg-image('star24_off')
    </style>
    View Code

    步骤二:继续编写header.vue文件

    <template>
    <div class="header">
      <div class="content-wrapper">
        <div class="avatar">
          <img width="64" height="64" :src="seller.avatar">
        </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" :class="classMap[seller.supports[0].type]"></span>
            <span class="text">{{seller.supports[0].description}}</span>
          </div>
        </div>
        <div class="support-count" v-if="seller.supports" @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 :src="seller.avatar" width="100%" height="100%">
      </div>
    
      <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="36" :score="seller.score"></star>
            </div>
          </div>
        </div>
        <div class="detail-close">
          <i class="icon-close"></i>
        </div>
      </div>
    </div>
    </template>
    
    <script>
    import star from '../../components/star/star';
    export default {
      name: 'v-header',
      props: {
          seller: {
              type: Object
          }
      },
      data() {
          return {
              detailShow: false
          };
      },
      methods: {
          showDetail() {
              this.detailShow = true;
          }
      },
      created() {
          this.classMap = ['decrease','discount','special','invoice','guarantee'];
      },
        components: {
          star
        }
    }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .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
                    .title
                        margin: 2px 0 8px 0
                        .brand
                            display: inline-block
                            vertical-align: top
                             30px
                            height:18px
                            bg-image("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
                            height: 12px
                             12px
                            margin-right: 4px
                            background-size: 12px 12px
                            background-repeat: no-repeat
                            &.decrease
                                bg-image('decrease_1')
                            &.discount
                                bg-image('discount_1')
                            &.guarantee
                                bg-image('guarantee_1')
                            &.invoice
                                bg-image('invoice_1')
                            &.special
                                bg-image('special_1')
                        .text
                            font-size: 10px
                            line-height: 12px
                .support-count
                    position: absolute
                    right: 12px
                    bottom: 14px
                    padding: 0 8px
                    height: 24px
                    line-height: 24px
                    border-radius: 14px
                    background: rgba(0, 0, 0, 0.2)
                    text-align: center
                    .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
                     22px
                    height: 12px
                    bg-image('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
                 100%
                height: 100%
                z-index: -1
                /*增加模糊度*/
                filter: blur(5px)
            .detail
                position: fixed
                top: 0
                left: 0
                z-index: 100
                height: 100%
                 100%
                overflow: auto
                background: rgba(7, 17, 27, 0.8)
                .detail-wrapper
                    min-height: 100%
                     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: 18px
                            padding: 2px 0
                            text-align: center
                .detail-close
                    position: relative
                     32px
                    height: 32px
                    margin: -64px auto 0 auto
                    clear: both
                    font-size: 32px
    </style>
    View Code

    详情弹层页(五)

    继续编写header.vue文件,增加响应式的水平线条

    <template>
    <div class="header">
      <div class="content-wrapper">
        <div class="avatar">
          <img width="64" height="64" :src="seller.avatar">
        </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" :class="classMap[seller.supports[0].type]"></span>
            <span class="text">{{seller.supports[0].description}}</span>
          </div>
        </div>
        <div class="support-count" v-if="seller.supports" @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 :src="seller.avatar" width="100%" height="100%">
      </div>
    
      <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="36" :score="seller.score"></star>
            </div>
            <div class="title">
                <div class="line"></div>
                <div class="text">优惠信息</div>
                <div class="line"></div>
            </div>
          </div>
        </div>
        <div class="detail-close">
          <i class="icon-close"></i>
        </div>
      </div>
    </div>
    </template>
    
    <script>
    import star from '../../components/star/star';
    export default {
      name: 'v-header',
      props: {
          seller: {
              type: Object
          }
      },
      data() {
          return {
              detailShow: false
          };
      },
      methods: {
          showDetail() {
              this.detailShow = true;
          }
      },
      created() {
          this.classMap = ['decrease','discount','special','invoice','guarantee'];
      },
        components: {
          star
        }
    }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .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
                    .title
                        margin: 2px 0 8px 0
                        .brand
                            display: inline-block
                            vertical-align: top
                             30px
                            height:18px
                            bg-image("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
                            height: 12px
                             12px
                            margin-right: 4px
                            background-size: 12px 12px
                            background-repeat: no-repeat
                            &.decrease
                                bg-image('decrease_1')
                            &.discount
                                bg-image('discount_1')
                            &.guarantee
                                bg-image('guarantee_1')
                            &.invoice
                                bg-image('invoice_1')
                            &.special
                                bg-image('special_1')
                        .text
                            font-size: 10px
                            line-height: 12px
                .support-count
                    position: absolute
                    right: 12px
                    bottom: 14px
                    padding: 0 8px
                    height: 24px
                    line-height: 24px
                    border-radius: 14px
                    background: rgba(0, 0, 0, 0.2)
                    text-align: center
                    .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
                     22px
                    height: 12px
                    bg-image('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
                 100%
                height: 100%
                z-index: -1
                /*增加模糊度*/
                filter: blur(5px)
            .detail
                position: fixed
                top: 0
                left: 0
                z-index: 100
                height: 100%
                 100%
                overflow: auto
                background: rgba(7, 17, 27, 0.8)
                .detail-wrapper
                    min-height: 100%
                     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: 18px
                            padding: 2px 0
                            text-align: center
                        .title
                            display: flex
                             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-weight: 700
                                font-size: 14px
                            
                .detail-close
                    position: relative
                     32px
                    height: 32px
                    margin: -64px auto 0 auto
                    clear: both
                    font-size: 32px
    </style>
    View Code

    食品组件布局(一)

    创建goods.vue组件,首先编写食品左侧内容

    <template>
      <div class="goods">
        <div class="menu-wrapper">
                <ul>
                    <li v-for="item in goods" class="menu-item">
                        <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span>
                            {{item.name}}
                        </span>
                    </li>
                </ul>
        </div>
        <div class="foods-wrapper">
            
        </div>
      </div>
    </template>
    
    <script>
        const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
            seller: {
                type: Object
            }
        },
        data() {
            return {
                goods: []
            };
        },
        created() {
            this.classMap = ['decrease','discount','special','invoice','guarantee'];
            this.$http.get('api/goods').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.goods = response.data;
              }
              console.log(response.data);
              this.goods = response.data;
            })
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
    </style>
    View Code

    食品组件布局(二)

    编写goods.vue组件,增加右侧内容

    <template>
    <div class="goods">
      <div class="menu-wrapper">
        <ul>
          <li v-for="item in goods" class="menu-item">
            <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
            </span>
          </li>
        </ul>
      </div>
      <div class="foods-wrapper">
        <ul>
          <li v-for="item in goods" class="food-list">
            <h1 class="title">{{item.name}}</h1>
            <ul>
              <li v-for="food in item.foods" class="food-item">
                <div class="icon">
                  <img :src="food.icon" />
                </div>
                <div class="content">
                  <h2 class="name">{{food.name}}</h2>
                  <p class="desc">{{food.description}}</p>
                  <div class="extra">
                    <span>月售{{food.sellCount}}份</span>
                    <span>好评率{{food.rating}}%</span>
                  </div>
                  <div class="price">
                    <span>¥{{food.price}}</span>
                    <span v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                  </div>
                </div>
              </li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
    </template>
    
    <script>
        const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
            seller: {
                type: Object
            }
        },
        data() {
            return {
                goods: []
            };
        },
        created() {
            this.classMap = ['decrease','discount','special','invoice','guarantee'];
            this.$http.get('api/goods').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.goods = response.data;
              }
              console.log(response.data);
              this.goods = response.data;
            })
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
    </style>
    View Code

    食品组件布局(三)

    继续编写goods.vue组件,增加样式

    <template>
    <div class="goods">
      <div class="menu-wrapper">
        <ul>
          <li v-for="item in goods" class="menu-item">
            <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
            </span>
          </li>
        </ul>
      </div>
      <div class="foods-wrapper">
        <ul>
          <li v-for="item in goods" class="food-list">
            <h1 class="title">{{item.name}}</h1>
            <ul>
              <li v-for="food in item.foods" class="food-item border-1px">
                <div class="icon">
                  <img :src="food.icon" width="57" height="57" />
                </div>
                <div class="content">
                  <h2 class="name">{{food.name}}</h2>
                  <p class="desc">{{food.description}}</p>
                  <div class="extra">
                    <span>月售{{food.sellCount}}份</span>
                    <span>好评率{{food.rating}}%</span>
                  </div>
                  <div class="price">
                    <span>¥{{food.price}}</span>
                    <span v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                  </div>
                </div>
              </li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
    </template>
    
    <script>
        const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
            seller: {
                type: Object
            }
        },
        data() {
            return {
                goods: []
            };
        },
        created() {
            this.classMap = ['decrease','discount','special','invoice','guarantee'];
            this.$http.get('api/goods').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.goods = response.data;
              }
              console.log(response.data);
              this.goods = response.data;
            })
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
    </style>
    View Code

    食品组件布局(四)

    继续编写goods.vue组件,完成样式部分

    <template>
    <div class="goods">
      <div class="menu-wrapper">
        <ul>
          <li v-for="item in goods" class="menu-item">
            <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
            </span>
          </li>
        </ul>
      </div>
      <div class="foods-wrapper">
        <ul>
          <li v-for="item in goods" class="food-list">
            <h1 class="title">{{item.name}}</h1>
            <ul>
              <li v-for="food in item.foods" class="food-item border-1px">
                <div class="icon">
                  <img :src="food.icon" width="57" height="57" />
                </div>
                <div class="content">
                  <h2 class="name">{{food.name}}</h2>
                  <p class="desc">{{food.description}}</p>
                  <div class="extra">
                    <span class="count">月售{{food.sellCount}}份</span>
                    <span>好评率{{food.rating}}%</span>
                  </div>
                  <div class="price">
                    <span class="now">¥{{food.price}}</span>
                    <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                  </div>
                </div>
              </li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
    </template>
    
    <script>
        const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
            seller: {
                type: Object
            }
        },
        data() {
            return {
                goods: []
            };
        },
        created() {
            this.classMap = ['decrease','discount','special','invoice','guarantee'];
            this.$http.get('api/goods').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.goods = response.data;
              }
              console.log(response.data);
              this.goods = response.data;
            })
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
                        .desc, .extra                    
                            line-height: 10px
                            font-size: 10px
                            color: rgb(147,153,159)
                        .desc
                            margin-bottom: 8px
                        .extra
                            &.count
                                margin-right: 12px
                        .price
                            font-weight: 700
                            line-height: 24px
                            .now
                                margin-right: 8px
                                font-size: 14px
                                color: rgb(240,20,20)
                            .old
                                text-decoration: line-through
                                font-size: 10px
                                color: rgb(147,153,159)
                            
    </style>
    View Code

    使用better-scroll(一)

    插件地址:https://github.com/ustbhuangyi/better-scroll

    步骤一:下载安装cnpm install better-scroll --save

    步骤二:编写goods.vue组件,让页面滚动起来

    <template>
    <div class="goods">
      <div class="menu-wrapper" ref="menuWrapper">
        <ul>
          <li v-for="item in goods" class="menu-item">
            <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
            </span>
          </li>
        </ul>
      </div>
      <div class="foods-wrapper" ref="foodsWrapper">
        <ul>
          <li v-for="item in goods" class="food-list">
            <h1 class="title">{{item.name}}</h1>
            <ul>
              <li v-for="food in item.foods" class="food-item border-1px">
                <div class="icon">
                  <img :src="food.icon" width="57" height="57" />
                </div>
                <div class="content">
                  <h2 class="name">{{food.name}}</h2>
                  <p class="desc">{{food.description}}</p>
                  <div class="extra">
                    <span class="count">月售{{food.sellCount}}份</span>
                    <span>好评率{{food.rating}}%</span>
                  </div>
                  <div class="price">
                    <span class="now">¥{{food.price}}</span>
                    <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                  </div>
                </div>
              </li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
    </template>
    
    <script>
        import BScroll from 'better-scroll'
        const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
            seller: {
                type: Object
            }
        },
        data() {
            return {
                goods: []
            };
        },
        created() {
            this.classMap = ['decrease','discount','special','invoice','guarantee'];
            this.$http.get('api/goods').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.goods = response.data;              
              }
              this.goods = response.data;
              this.$nextTick(() => {
                      this._initScroll();
              });
            });
        },
        methods: {
             _initScroll() {
                 this.menuScroll = new BScroll(this.$refs.menuWrapper,{});
                 
                 this.foodsScroll = new BScroll(this.$refs.foodsWrapper,{});
             }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
                        .desc, .extra                    
                            line-height: 10px
                            font-size: 10px
                            color: rgb(147,153,159)
                        .desc
                            margin-bottom: 8px
                            line-height: 14px
                        .extra
                            .count
                                margin-right: 12px
                        .price
                            font-weight: 700
                            line-height: 24px
                            .now
                                margin-right: 8px
                                font-size: 14px
                                color: rgb(240,20,20)
                            .old
                                text-decoration: line-through
                                font-size: 10px
                                color: rgb(147,153,159)
                            
    </style>
    View Code

    使用better-scroll(二)

    编写goods.vue组件,监测右侧页面高度

    <template>
    <div class="goods">
      <div class="menu-wrapper" ref="menuWrapper">
        <ul>
          <li v-for="item in goods" class="menu-item">
            <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
            </span>
          </li>
        </ul>
      </div>
      <div class="foods-wrapper" ref="foodsWrapper">
        <ul>
          <li v-for="item in goods" class="food-list food-list-hook">
            <h1 class="title">{{item.name}}</h1>
            <ul>
              <li v-for="food in item.foods" class="food-item border-1px">
                <div class="icon">
                  <img :src="food.icon" width="57" height="57" />
                </div>
                <div class="content">
                  <h2 class="name">{{food.name}}</h2>
                  <p class="desc">{{food.description}}</p>
                  <div class="extra">
                    <span class="count">月售{{food.sellCount}}份</span>
                    <span>好评率{{food.rating}}%</span>
                  </div>
                  <div class="price">
                    <span class="now">¥{{food.price}}</span>
                    <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                  </div>
                </div>
              </li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
    </template>
    
    <script>
        import BScroll from 'better-scroll'
        const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
            seller: {
                type: Object
            }
        },
        data() {
            return {
                goods: [],
                listHeight: [],
                scrollY: 0
            };
        },
        computed: {
            currentIndex() {
                for(let i=0;i<this.listHeight.length;i++){
                    let height1 = this.listHeight[i];
                    let height2 = this.listHeight[i+1];
                    if (!height2 || (this.scrollY > height1 && this.scrollY <height2)){
                        return i;
                    }
                }
                return 0;
            }
        },
        created() {
            this.classMap = ['decrease','discount','special','invoice','guarantee'];
            this.$http.get('api/goods').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.goods = response.data;              
              }
              this.goods = response.data;
              this.$nextTick(() => {
                      this._initScroll();
                      this._calculateHeight();
              });
            });
        },
        methods: {
             _initScroll() {
                 this.menuScroll = new BScroll(this.$refs.menuWrapper,{});
                 
                 this.foodsScroll = new BScroll(this.$refs.foodsWrapper,{
                     probeType: 3
                 });
                 
                 this.foodsScroll.on("scroll",(pos) => {
                     this.scrollY = Math.abs(Math.round(pos.y));
                 });
             },
             _calculateHeight() {
                 let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
                let height = 0;
                this.listHeight.push(height);
                for(let i=0;i<foodList.length;i++){
                    let item = foodList[i];
                    height += item.clientHeight;
                    this.listHeight.push(height);
                }
             }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
                        .desc, .extra                    
                            line-height: 10px
                            font-size: 10px
                            color: rgb(147,153,159)
                        .desc
                            margin-bottom: 8px
                            line-height: 14px
                        .extra
                            .count
                                margin-right: 12px
                        .price
                            font-weight: 700
                            line-height: 24px
                            .now
                                margin-right: 8px
                                font-size: 14px
                                color: rgb(240,20,20)
                            .old
                                text-decoration: line-through
                                font-size: 10px
                                color: rgb(147,153,159)
                            
    </style>
    View Code

    使用better-scroll(三)

    编写goods.vue组件,实现右侧滚动左边实时发生变化

    <template>
    <div class="goods">
      <div class="menu-wrapper" ref="menuWrapper">
        <ul>
          <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex===index}">
            <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
            </span>
          </li>
        </ul>
      </div>
      <div class="foods-wrapper" ref="foodsWrapper">
        <ul>
          <li v-for="item in goods" class="food-list food-list-hook">
            <h1 class="title">{{item.name}}</h1>
            <ul>
              <li v-for="food in item.foods" class="food-item border-1px">
                <div class="icon">
                  <img :src="food.icon" width="57" height="57" />
                </div>
                <div class="content">
                  <h2 class="name">{{food.name}}</h2>
                  <p class="desc">{{food.description}}</p>
                  <div class="extra">
                    <span class="count">月售{{food.sellCount}}份</span>
                    <span>好评率{{food.rating}}%</span>
                  </div>
                  <div class="price">
                    <span class="now">¥{{food.price}}</span>
                    <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                  </div>
                </div>
              </li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
    </template>
    
    <script>
        import BScroll from 'better-scroll'
        const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
            seller: {
                type: Object
            }
        },
        data() {
            return {
                goods: [],
                listHeight: [],
                scrollY: 0
            };
        },
        computed: {
            currentIndex() {
                for(let i=0;i<this.listHeight.length;i++){
                    let height1 = this.listHeight[i];
                    let height2 = this.listHeight[i+1];
                    if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)){
                        return i;
                    }
                }
                return 0;
            }
        },
        created() {
            this.classMap = ['decrease','discount','special','invoice','guarantee'];
            this.$http.get('api/goods').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.goods = response.data;              
              }
              this.goods = response.data;
              this.$nextTick(() => {
                      this._initScroll();
                      this._calculateHeight();
              });
            });
        },
        methods: {
             _initScroll() {
                 this.menuScroll = new BScroll(this.$refs.menuWrapper,{});
                 
                 this.foodsScroll = new BScroll(this.$refs.foodsWrapper,{
                     probeType: 3
                 });
                 
                 this.foodsScroll.on("scroll",(pos) => {
                     this.scrollY = Math.abs(Math.round(pos.y));
                 });
             },
             _calculateHeight() {
                 let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
                let height = 0;
                this.listHeight.push(height);
                for(let i=0;i<foodList.length;i++){
                    let item = foodList[i];
                    height += item.clientHeight;
                    this.listHeight.push(height);
                }
             }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    &.current
                        position: relative
                        z-index: 10
                        margin-top: -1px
                        background: #ccc
                        font-weight: 700
                        .text
                            border-none()
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
                        .desc, .extra                    
                            line-height: 10px
                            font-size: 10px
                            color: rgb(147,153,159)
                        .desc
                            margin-bottom: 8px
                            line-height: 14px
                        .extra
                            .count
                                margin-right: 12px
                        .price
                            font-weight: 700
                            line-height: 24px
                            .now
                                margin-right: 8px
                                font-size: 14px
                                color: rgb(240,20,20)
                            .old
                                text-decoration: line-through
                                font-size: 10px
                                color: rgb(147,153,159)
                            
    </style>
    View Code

    使用better-scroll(四)

    编写goods.vue组价,完全实现左右联动

    <template>
    <div class="goods">
      <div class="menu-wrapper" ref="menuWrapper">
        <ul>
          <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex===index}" @click="selectMenu(index,$event)">
            <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
            </span>
          </li>
        </ul>
      </div>
      <div class="foods-wrapper" ref="foodsWrapper">
        <ul>
          <li v-for="item in goods" class="food-list food-list-hook">
            <h1 class="title">{{item.name}}</h1>
            <ul>
              <li v-for="food in item.foods" class="food-item border-1px">
                <div class="icon">
                  <img :src="food.icon" width="57" height="57" />
                </div>
                <div class="content">
                  <h2 class="name">{{food.name}}</h2>
                  <p class="desc">{{food.description}}</p>
                  <div class="extra">
                    <span class="count">月售{{food.sellCount}}份</span>
                    <span>好评率{{food.rating}}%</span>
                  </div>
                  <div class="price">
                    <span class="now">¥{{food.price}}</span>
                    <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                  </div>
                </div>
              </li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
    </template>
    
    <script>
        import BScroll from 'better-scroll'
        const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
            seller: {
                type: Object
            }
        },
        data() {
            return {
                goods: [],
                listHeight: [],
                scrollY: 0
            };
        },
        computed: {
            currentIndex() {
                for(let i=0;i<this.listHeight.length;i++){
                    let height1 = this.listHeight[i];
                    let height2 = this.listHeight[i+1];
                    if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)){
                        return i;
                    }
                }
                return 0;
            }
        },
        created() {
            this.classMap = ['decrease','discount','special','invoice','guarantee'];
            this.$http.get('api/goods').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.goods = response.data;              
              }
              this.goods = response.data;
              this.$nextTick(() => {
                      this._initScroll();
                      this._calculateHeight();
              });
            });
        },
        methods: {
            selectMenu(index,event){
                  if(!event._constructed){
                      return;
                  }
                  let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
                  let el = foodList[index];
                  this.foodsScroll.scrollToElement(el,300);
                  console.log(index);
              },
             _initScroll() {
                 this.menuScroll = new BScroll(this.$refs.menuWrapper,{
                     click: true
                 });
                 
                 this.foodsScroll = new BScroll(this.$refs.foodsWrapper,{
                     click: true,
                     probeType: 3
                 });
                 
                 this.foodsScroll.on("scroll",(pos) => {
                     this.scrollY = Math.abs(Math.round(pos.y));
                 });
             },
             _calculateHeight() {
                 let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
                let height = 0;
                this.listHeight.push(height);
                for(let i=0;i<foodList.length;i++){
                    let item = foodList[i];
                    height += item.clientHeight;
                    this.listHeight.push(height);
                }
             }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    &.current
                        position: relative
                        z-index: 10
                        margin-top: -1px
                        background: #ccc
                        font-weight: 700
                        .text
                            border-none()
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
                        .desc, .extra                    
                            line-height: 10px
                            font-size: 10px
                            color: rgb(147,153,159)
                        .desc
                            margin-bottom: 8px
                            line-height: 14px
                        .extra
                            .count
                                margin-right: 12px
                        .price
                            font-weight: 700
                            line-height: 24px
                            .now
                                margin-right: 8px
                                font-size: 14px
                                color: rgb(240,20,20)
                            .old
                                text-decoration: line-through
                                font-size: 10px
                                color: rgb(147,153,159)
                            
    </style>
    View Code

    购物车组件(一)

    步骤一:创建一个shopcart.vue组件,编写好基础的样式

    <template>
      <div class="shopcart">
        <div class="content">
          <div class="content-left">
            <div class="logo-wrapper">
              <div class="logo">
                <span class="icon-shopping_cart"></span>
              </div>
            </div>
            <div class="price"></div>
            <div class="desc"></div>
          </div>
          <div class="content-right"></div>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        name: 'v-goods'
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .shopcart
        position: fixed
        left: 0
        bottom: 0
        z-index: 50
         100%
        height: 48px
        background: #000
    </style>
    View Code

    步骤二:配置好goods.vue组件

    <template>
    <div class="goods">
      <div class="menu-wrapper" ref="menuWrapper">
        <ul>
          <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex===index}" @click="selectMenu(index,$event)">
            <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
            </span>
          </li>
        </ul>
      </div>
      <div class="foods-wrapper" ref="foodsWrapper">
        <ul>
          <li v-for="item in goods" class="food-list food-list-hook">
            <h1 class="title">{{item.name}}</h1>
            <ul>
              <li v-for="food in item.foods" class="food-item border-1px">
                <div class="icon">
                  <img :src="food.icon" width="57" height="57" />
                </div>
                <div class="content">
                  <h2 class="name">{{food.name}}</h2>
                  <p class="desc">{{food.description}}</p>
                  <div class="extra">
                    <span class="count">月售{{food.sellCount}}份</span>
                    <span>好评率{{food.rating}}%</span>
                  </div>
                  <div class="price">
                    <span class="now">¥{{food.price}}</span>
                    <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                  </div>
                </div>
              </li>
            </ul>
          </li>
        </ul>
      </div>
      <shopcart></shopcart>
    </div>
    </template>
    
    <script>
        import BScroll from 'better-scroll';
        import shopcart from '../../components/shopcart/shopcart';
        const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
            seller: {
                type: Object
            }
        },
        data() {
            return {
                goods: [],
                listHeight: [],
                scrollY: 0
            };
        },
        computed: {
            currentIndex() {
                for(let i=0;i<this.listHeight.length;i++){
                    let height1 = this.listHeight[i];
                    let height2 = this.listHeight[i+1];
                    if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)){
                        return i;
                    }
                }
                return 0;
            }
        },
        created() {
            this.classMap = ['decrease','discount','special','invoice','guarantee'];
            this.$http.get('api/goods').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.goods = response.data;              
              }
              this.goods = response.data;
              this.$nextTick(() => {
                      this._initScroll();
                      this._calculateHeight();
              });
            });
        },
        methods: {
            selectMenu(index,event){
                  if(!event._constructed){
                      return;
                  }
                  let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
                  let el = foodList[index];
                  this.foodsScroll.scrollToElement(el,300);
                  console.log(index);
              },
             _initScroll() {
                 this.menuScroll = new BScroll(this.$refs.menuWrapper,{
                     click: true
                 });
                 
                 this.foodsScroll = new BScroll(this.$refs.foodsWrapper,{
                     click: true,
                     probeType: 3
                 });
                 
                 this.foodsScroll.on("scroll",(pos) => {
                     this.scrollY = Math.abs(Math.round(pos.y));
                 });
             },
             _calculateHeight() {
                 let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
                let height = 0;
                this.listHeight.push(height);
                for(let i=0;i<foodList.length;i++){
                    let item = foodList[i];
                    height += item.clientHeight;
                    this.listHeight.push(height);
                }
             }
        },
        components: {
            shopcart
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    &.current
                        position: relative
                        z-index: 10
                        margin-top: -1px
                        background: #ccc
                        font-weight: 700
                        .text
                            border-none()
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
                        .desc, .extra                    
                            line-height: 10px
                            font-size: 10px
                            color: rgb(147,153,159)
                        .desc
                            margin-bottom: 8px
                            line-height: 14px
                        .extra
                            .count
                                margin-right: 12px
                        .price
                            font-weight: 700
                            line-height: 24px
                            .now
                                margin-right: 8px
                                font-size: 14px
                                color: rgb(240,20,20)
                            .old
                                text-decoration: line-through
                                font-size: 10px
                                color: rgb(147,153,159)
                            
    </style>
    View Code

    购物车组件(二)

    继续编写shopcart.vue组件,增加样式

    <template>
      <div class="shopcart">
        <div class="content">
          <div class="content-left">
            <div class="logo-wrapper">
              <div class="logo">
                <span class="icon-shopping_cart"></span>
              </div>
            </div>
            <div class="price"></div>
            <div class="desc"></div>
          </div>
          <div class="content-right"></div>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        name: 'v-goods'
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .shopcart
        position: fixed
        left: 0
        bottom: 0
        z-index: 50
         100%
        height: 48px
        .content
            display: flex
            background: #141d27
            font-size: 0
            .content-left
                flex: 1
                .logo-wrapper
                    display: inline-block
                    position: relative
                    top:-10px
                    margin: 0 12px
                    padding: 6px
                     56px
                    height: 56px
                    vertical-align: top
                    box-sizing: border-box
                    border-radius: 50%
                    background: #141d27
                    .logo
                         100%
                        height: 100%
                        border-radius: 50%
                        text-align: center
                        background: #2b343c
                        .icon-shopping_cart
                            font-size: 24px
                            line-height: 44px
                            color: #80858a
                .price
                    display: inline-block
                    vertical-align: top
                    margin-top: 12px
                    line-height: 24px
                    padding-right: 12px
                    box-sizing: border-box
                    border-right: 1px solid rgba(255,255,255,0.1)
                    font-size: 16px
                    font-weight: 700
                .desc
                    display: inline-block
            .content-right
                flex: 0 0 105px
                 105px
    </style>
    View Code

    购物车组件(三)

    步骤一:修改App.vue根组价,传递值

    <template>
      <div>
        <v-header :seller="seller"></v-header>
        <div class="tab border-1px">
          <div class="tab-item">
              <router-link to="/goods">商品</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/ratings">评价</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/seller">商家</router-link>
          </div>
        </div>
        <router-view :seller="seller"></router-view>
      </div>
    </template>
    
    <script>
      import header from './components/header/header.vue';
      const ERR_OK = 0;
      export default {
        name: 'app',
        data() {
            return {
                seller: {}
            }
        },
        created() {
            this.$http.get('api/seller').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.seller = response.data;
              }
              this.seller = response.data;
            })
        },
        components: {
          'v-header': header
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "./common/stylus/mixin.styl";
      .tab
        display: flex
         100%
        height: 40px
        line-height: 40px
        /*border-bottom: 1px solid rgba(7, 17, 27, 0.1)*/
        border-1px(rgba(7, 17, 27, 0.1))
        .tab-item
          flex: 1
          text-align: center
          & > a
           display:block
           font-size:16px
           color:rgb(77,85,93)
           &.active
            color:rgb(240,20,20)
    </style>
    View Code

    步骤二:修改goods.vue组件,传递值

    <template>
    <div class="goods">
      <div class="menu-wrapper" ref="menuWrapper">
        <ul>
          <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex===index}" @click="selectMenu(index,$event)">
            <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
            </span>
          </li>
        </ul>
      </div>
      <div class="foods-wrapper" ref="foodsWrapper">
        <ul>
          <li v-for="item in goods" class="food-list food-list-hook">
            <h1 class="title">{{item.name}}</h1>
            <ul>
              <li v-for="food in item.foods" class="food-item border-1px">
                <div class="icon">
                  <img :src="food.icon" width="57" height="57" />
                </div>
                <div class="content">
                  <h2 class="name">{{food.name}}</h2>
                  <p class="desc">{{food.description}}</p>
                  <div class="extra">
                    <span class="count">月售{{food.sellCount}}份</span>
                    <span>好评率{{food.rating}}%</span>
                  </div>
                  <div class="price">
                    <span class="now">¥{{food.price}}</span>
                    <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                  </div>
                </div>
              </li>
            </ul>
          </li>
        </ul>
      </div>
      <shopcart :delivery-price="seller.deliveryPrice" :min-price="seller.minPrice"></shopcart>
    </div>
    </template>
    
    <script>
        import BScroll from 'better-scroll';
        import shopcart from '../../components/shopcart/shopcart';
        const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
            seller: {
                type: Object
            }
        },
        data() {
            return {
                goods: [],
                listHeight: [],
                scrollY: 0
            };
        },
        computed: {
            currentIndex() {
                for(let i=0;i<this.listHeight.length;i++){
                    let height1 = this.listHeight[i];
                    let height2 = this.listHeight[i+1];
                    if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)){
                        return i;
                    }
                }
                return 0;
            }
        },
        created() {
            this.classMap = ['decrease','discount','special','invoice','guarantee'];
            this.$http.get('api/goods').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.goods = response.data;              
              }
              this.goods = response.data;
              this.$nextTick(() => {
                      this._initScroll();
                      this._calculateHeight();
              });
            });
        },
        methods: {
            selectMenu(index,event){
                  if(!event._constructed){
                      return;
                  }
                  let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
                  let el = foodList[index];
                  this.foodsScroll.scrollToElement(el,300);
                  console.log(index);
              },
             _initScroll() {
                 this.menuScroll = new BScroll(this.$refs.menuWrapper,{
                     click: true
                 });
                 
                 this.foodsScroll = new BScroll(this.$refs.foodsWrapper,{
                     click: true,
                     probeType: 3
                 });
                 
                 this.foodsScroll.on("scroll",(pos) => {
                     this.scrollY = Math.abs(Math.round(pos.y));
                 });
             },
             _calculateHeight() {
                 let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
                let height = 0;
                this.listHeight.push(height);
                for(let i=0;i<foodList.length;i++){
                    let item = foodList[i];
                    height += item.clientHeight;
                    this.listHeight.push(height);
                }
             }
        },
        components: {
            shopcart
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    &.current
                        position: relative
                        z-index: 10
                        margin-top: -1px
                        background: #ccc
                        font-weight: 700
                        .text
                            border-none()
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
                        .desc, .extra                    
                            line-height: 10px
                            font-size: 10px
                            color: rgb(147,153,159)
                        .desc
                            margin-bottom: 8px
                            line-height: 14px
                        .extra
                            .count
                                margin-right: 12px
                        .price
                            font-weight: 700
                            line-height: 24px
                            .now
                                margin-right: 8px
                                font-size: 14px
                                color: rgb(240,20,20)
                            .old
                                text-decoration: line-through
                                font-size: 10px
                                color: rgb(147,153,159)
                            
    </style>
    View Code

    步骤三:编写shopcart.vue组件,接收值并增加样式

    <template>
      <div class="shopcart">
        <div class="content">
          <div class="content-left">
            <div class="logo-wrapper">
              <div class="logo">
                <span class="icon-shopping_cart"></span>
              </div>
            </div>
            <div class="price">0元</div>
            <div class="desc">另需配送费¥{{deliveryPrice}}元</div>
          </div>
          <div class="content-right"></div>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        name: 'v-shopcart',
        props: {
            deliveryPrice: {
                type: Number,
                default: 0
            },
            minPrice: {
                type: Number,
                default: 0
            }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .shopcart
        position: fixed
        left: 0
        bottom: 0
        z-index: 50
         100%
        height: 48px
        .content
            display: flex
            background: #141d27
            font-size: 0
            .content-left
                flex: 1
                .logo-wrapper
                    display: inline-block
                    position: relative
                    top:-10px
                    margin: 0 12px
                    padding: 6px
                     56px
                    height: 56px
                    vertical-align: top
                    box-sizing: border-box
                    border-radius: 50%
                    background: #141d27
                    .logo
                         100%
                        height: 100%
                        border-radius: 50%
                        text-align: center
                        background: #2b343c
                        .icon-shopping_cart
                            font-size: 24px
                            line-height: 44px
                            color: #80858a
                .price
                    display: inline-block
                    vertical-align: top
                    margin-top: 12px
                    line-height: 24px
                    padding-right: 12px
                    box-sizing: border-box
                    border-right: 1px solid rgba(255,255,255,0.1)
                    font-size: 16px
                    font-weight: 700
                    color: rgba(255,255,255,0.4)
                .desc
                    display: inline-block
                    vertical-align: top
                    line-height: 24px
                    margin: 12px 0 0 12px
                    font-size: 10px
                    color: rgba(255,255,255,0.4)
            .content-right
                flex: 0 0 105px
                 105px
    </style>
    View Code

     购物车组件(四)

    继续编写shopcart.vue组件,为商品价格计算做前期编码

    <template>
      <div class="shopcart">
        <div class="content">
          <div class="content-left">
            <div class="logo-wrapper">
              <div class="logo">
                <span class="icon-shopping_cart"></span>
              </div>
            </div>
            <div class="price">¥{{totalPrice}}</div>
            <div class="desc">另需配送费¥{{deliveryPrice}}元</div>
          </div>
          <div class="content-right">
              <div class="pay">
                  ¥{{minPrice}}元起送
              </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        name: 'v-shopcart',
        props: {
            selectFoods: {
                type: Array,
                default() {
                    return [
                        {
                            price:10,
                            count:1
                        }
                    ];
                }
            },
            deliveryPrice: {
                type: Number,
                default: 0
            },
            minPrice: {
                type: Number,
                default: 0
            }
        },
        computed: {
            totalPrice() {
                let total = 0;
                this.selectFoods.forEach((food) => {
                    total += food.price * food.count;
                });
                return total;
            },
            totalCount() {
                let count = 0;
                this.selectFoods.forEach((food) => {
                    count += food.count;
                });
                return count;
            }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .shopcart
        position: fixed
        left: 0
        bottom: 0
        z-index: 50
         100%
        height: 48px
        .content
            display: flex
            background: #141d27
            font-size: 0
            .content-left
                flex: 1
                .logo-wrapper
                    display: inline-block
                    position: relative
                    top:-10px
                    margin: 0 12px
                    padding: 6px
                     56px
                    height: 56px
                    vertical-align: top
                    box-sizing: border-box
                    border-radius: 50%
                    background: #141d27
                    .logo
                         100%
                        height: 100%
                        border-radius: 50%
                        text-align: center
                        background: #2b343c
                        .icon-shopping_cart
                            font-size: 24px
                            line-height: 44px
                            color: #80858a
                .price
                    display: inline-block
                    vertical-align: top
                    margin-top: 12px
                    line-height: 24px
                    padding-right: 12px
                    box-sizing: border-box
                    border-right: 1px solid rgba(255,255,255,0.1)
                    font-size: 16px
                    font-weight: 700
                    color: rgba(255,255,255,0.4)
                .desc
                    display: inline-block
                    vertical-align: top
                    line-height: 24px
                    margin: 12px 0 0 12px
                    font-size: 10px
                    color: rgba(255,255,255,0.4)
            .content-right
                flex: 0 0 105px
                 105px
                .pay
                    height: 48px
                    line-height: 48px
                    text-align: center
                    font-size: 12px
                    color: rgba(255,255,255,0.4)
                    font-weight: 700
                    background: #2b333b
    </style>
    View Code

    购物车组件(五)

    继续编写shopcart.vue组件,实现样式动态改变

    <template>
      <div class="shopcart">
        <div class="content">
          <div class="content-left">
            <div class="logo-wrapper">
              <div class="logo" :class="{'highlight':totalCount>0}">
                <span class="icon-shopping_cart" :class="{'highlight':totalCount>0}"></span>
              </div>
              <div class="num" v-show="totalCount>0">{{totalCount}}</div>
            </div>
            <div class="price" :class="{'highlight':totalPrice>0}">¥{{totalPrice}}</div>
            <div class="desc">另需配送费¥{{deliveryPrice}}元</div>
          </div>
          <div class="content-right">
              <div class="pay">
                  ¥{{minPrice}}元起送
              </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        name: 'v-shopcart',
        props: {
            selectFoods: {
                type: Array,
                default() {
                    return [];
                }
            },
            deliveryPrice: {
                type: Number,
                default: 0
            },
            minPrice: {
                type: Number,
                default: 0
            }
        },
        computed: {
            totalPrice() {
                let total = 0;
                this.selectFoods.forEach((food) => {
                    total += food.price * food.count;
                });
                return total;
            },
            totalCount() {
                let count = 0;
                this.selectFoods.forEach((food) => {
                    count += food.count;
                });
                return count;
            }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .shopcart
        position: fixed
        left: 0
        bottom: 0
        z-index: 50
         100%
        height: 48px
        .content
            display: flex
            background: #141d27
            font-size: 0
            .content-left
                flex: 1
                .logo-wrapper
                    display: inline-block
                    position: relative
                    top:-10px
                    margin: 0 12px
                    padding: 6px
                     56px
                    height: 56px
                    vertical-align: top
                    box-sizing: border-box
                    border-radius: 50%
                    background: #141d27
                    .logo
                         100%
                        height: 100%
                        border-radius: 50%
                        text-align: center
                        background: #2b343c
                        &.highlight
                            background: rgb(0,160,220)
                        .icon-shopping_cart
                            font-size: 24px
                            line-height: 44px
                            color: #80858a
                            &.highlight
                                color: #fff
                    .num
                        position: absolute
                        top: 0px
                        right: 0px
                         24px
                        height: 16px
                        line-height: 16px
                        text-align: center
                        border-radius: 16px
                        font-size: 9px
                        font-weight: 700
                        color: #fff
                        background: rgb(240,20,20)
                        box-shadow: 0 4px 8px 0 rgba(0,0,0,0.4)
                .price
                    display: inline-block
                    vertical-align: top
                    margin-top: 12px
                    line-height: 24px
                    padding-right: 12px
                    box-sizing: border-box
                    border-right: 1px solid rgba(255,255,255,0.1)
                    font-size: 16px
                    font-weight: 700
                    color: rgba(255,255,255,0.4)
                    &.highlight
                        color: #fff
                .desc
                    display: inline-block
                    vertical-align: top
                    line-height: 24px
                    margin: 12px 0 0 12px
                    font-size: 10px
                    color: rgba(255,255,255,0.4)
            .content-right
                flex: 0 0 105px
                 105px
                .pay
                    height: 48px
                    line-height: 48px
                    text-align: center
                    font-size: 12px
                    color: rgba(255,255,255,0.4)
                    font-weight: 700
                    background: #2b333b
    </style>
    View Code

    购物车组件(六)

    继续编写shopcart.vue组件,基本完成全部效果

    <template>
      <div class="shopcart">
        <div class="content">
          <div class="content-left">
            <div class="logo-wrapper">
              <div class="logo" :class="{'highlight':totalCount>0}">
                <span class="icon-shopping_cart" :class="{'highlight':totalCount>0}"></span>
              </div>
              <div class="num" v-show="totalCount>0">{{totalCount}}</div>
            </div>
            <div class="price" :class="{'highlight':totalPrice>0}">¥{{totalPrice}}</div>
            <div class="desc">另需配送费¥{{deliveryPrice}}元</div>
          </div>
          <div class="content-right">
              <div class="pay" :class="payClass">
                  {{payDesc}}
              </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        name: 'v-shopcart',
        props: {
            selectFoods: {
                type: Array,
                default() {
                    return [
                        {
                            price: 10,
                            count: 5
                        }
                    ];
                }
            },
            deliveryPrice: {
                type: Number,
                default: 0
            },
            minPrice: {
                type: Number,
                default: 0
            }
        },
        computed: {
            totalPrice() {
                let total = 0;
                this.selectFoods.forEach((food) => {
                    total += food.price * food.count;
                });
                return total;
            },
            totalCount() {
                let count = 0;
                this.selectFoods.forEach((food) => {
                    count += food.count;
                });
                return count;
            },
            payDesc() {
                if(this.totalPrice === 0){
                    return `¥${this.minPrice}元起送`;
                }else if(this.totalPrice<this.minPrice){
                    let diff = this.minPrice - this.totalPrice;
                    return `还差¥${diff}元起送`;
                }else{
                    return '去结算';
                }
            },
            payClass() {
                if(this.totalPrice < this.minPrice){
                    return 'not-enough';
                }else{
                    return 'enough';
                }
            }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .shopcart
        position: fixed
        left: 0
        bottom: 0
        z-index: 50
         100%
        height: 48px
        .content
            display: flex
            background: #141d27
            font-size: 0
            .content-left
                flex: 1
                .logo-wrapper
                    display: inline-block
                    position: relative
                    top:-10px
                    margin: 0 12px
                    padding: 6px
                     56px
                    height: 56px
                    vertical-align: top
                    box-sizing: border-box
                    border-radius: 50%
                    background: #141d27
                    .logo
                         100%
                        height: 100%
                        border-radius: 50%
                        text-align: center
                        background: #2b343c
                        &.highlight
                            background: rgb(0,160,220)
                        .icon-shopping_cart
                            font-size: 24px
                            line-height: 44px
                            color: #80858a
                            &.highlight
                                color: #fff
                    .num
                        position: absolute
                        top: 0px
                        right: 0px
                         24px
                        height: 16px
                        line-height: 16px
                        text-align: center
                        border-radius: 16px
                        font-size: 9px
                        font-weight: 700
                        color: #fff
                        background: rgb(240,20,20)
                        box-shadow: 0 4px 8px 0 rgba(0,0,0,0.4)
                .price
                    display: inline-block
                    vertical-align: top
                    margin-top: 12px
                    line-height: 24px
                    padding-right: 12px
                    box-sizing: border-box
                    border-right: 1px solid rgba(255,255,255,0.1)
                    font-size: 16px
                    font-weight: 700
                    color: rgba(255,255,255,0.4)
                    &.highlight
                        color: #fff
                .desc
                    display: inline-block
                    vertical-align: top
                    line-height: 24px
                    margin: 12px 0 0 12px
                    font-size: 10px
                    color: rgba(255,255,255,0.4)
            .content-right
                flex: 0 0 105px
                 105px
                .pay
                    height: 48px
                    line-height: 48px
                    text-align: center
                    font-size: 12px
                    color: rgba(255,255,255,0.4)
                    font-weight: 700
                    background: #2b333b
                    &.not-enough
                        background: #2b333b
                    &.enough
                        background: #00b43c
                        color: #fff
    </style>
    View Code

     cartcontrol组件(一)

    步骤一:新建cartcontrol.vue组件,作为购物车的添加按钮

    <template>
      <div class="cartcontrol">
        <div class="cart-decrease icon-remove_circle_outline" v-show="food.count>0"></div>
        <div class="cart-count" v-show="food.count>0">{{food.count}}</div>
        <div class="cart-add icon-add_circle" @click="addCart"></div>
      </div>
    </template>
    
    <script>
      export default {
        name: 'v-cartcontrol',
        props: {
          food: {
            type: Object
          }
        },
        created() {
          console.log(this.food);
        },
        methods: {
          addCart() {
    
          }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .cartcontrol
        font-size: 0
        .cart-decrease, .cart-add
          font-size: 24px
          line-height: 24px
          padding: 6px
          color: rgb(0,160,220)
          display: inline-block
        .cart-count
          display: inline-block
        .cart-add
          display: inline-block
    </style>
    View Code

    步骤二:在goods.vue组件中调用cartcontrol.vue组件

    <template>
    <div class="goods">
      <div class="menu-wrapper" ref="menuWrapper">
        <ul>
          <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex===index}" @click="selectMenu(index,$event)">
            <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
            </span>
          </li>
        </ul>
      </div>
      <div class="foods-wrapper" ref="foodsWrapper">
        <ul>
          <li v-for="item in goods" class="food-list food-list-hook">
            <h1 class="title">{{item.name}}</h1>
            <ul>
              <li v-for="food in item.foods" class="food-item border-1px">
                <div class="icon">
                  <img :src="food.icon" width="57" height="57" />
                </div>
                <div class="content">
                  <h2 class="name">{{food.name}}</h2>
                  <p class="desc">{{food.description}}</p>
                  <div class="extra">
                    <span class="count">月售{{food.sellCount}}份</span>
                    <span>好评率{{food.rating}}%</span>
                  </div>
                  <div class="price">
                    <span class="now">¥{{food.price}}</span>
                    <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                  </div>
                  <div class="cartcontrol-wrapper">
                      <cartcontrol :food="food"></cartcontrol>
                  </div>
                </div>
              </li>
            </ul>
          </li>
        </ul>
      </div>
      <shopcart :delivery-price="seller.deliveryPrice" :min-price="seller.minPrice"></shopcart>
    </div>
    </template>
    
    <script>
        import BScroll from 'better-scroll';
        import shopcart from '../../components/shopcart/shopcart';
        import cartcontrol from '../../components/cartcontrol/cartcontrol';
        const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
            seller: {
                type: Object
            }
        },
        data() {
            return {
                goods: [],
                listHeight: [],
                scrollY: 0
            };
        },
        computed: {
            currentIndex() {
                for(let i=0;i<this.listHeight.length;i++){
                    let height1 = this.listHeight[i];
                    let height2 = this.listHeight[i+1];
                    if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)){
                        return i;
                    }
                }
                return 0;
            }
        },
        created() {
            this.classMap = ['decrease','discount','special','invoice','guarantee'];
            this.$http.get('api/goods').then((response) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.goods = response.data;              
              }
              this.goods = response.data;
              this.$nextTick(() => {
                      this._initScroll();
                      this._calculateHeight();
              });
            });
        },
        methods: {
            selectMenu(index,event){
                  if(!event._constructed){
                      return;
                  }
                  let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
                  let el = foodList[index];
                  this.foodsScroll.scrollToElement(el,300);
                  console.log(index);
              },
             _initScroll() {
                 this.menuScroll = new BScroll(this.$refs.menuWrapper,{
                     click: true
                 });
                 
                 this.foodsScroll = new BScroll(this.$refs.foodsWrapper,{
                     click: true,
                     probeType: 3
                 });
                 
                 this.foodsScroll.on("scroll",(pos) => {
                     this.scrollY = Math.abs(Math.round(pos.y));
                 });
             },
             _calculateHeight() {
                 let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
                let height = 0;
                this.listHeight.push(height);
                for(let i=0;i<foodList.length;i++){
                    let item = foodList[i];
                    height += item.clientHeight;
                    this.listHeight.push(height);
                }
             }
        },
        components: {
            shopcart,
            cartcontrol
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    &.current
                        position: relative
                        z-index: 10
                        margin-top: -1px
                        background: #ccc
                        font-weight: 700
                        .text
                            border-none()
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
                        .desc, .extra                    
                            line-height: 10px
                            font-size: 10px
                            color: rgb(147,153,159)
                        .desc
                            margin-bottom: 8px
                            line-height: 14px
                        .extra
                            .count
                                margin-right: 12px
                        .price
                            font-weight: 700
                            line-height: 24px
                            .now
                                margin-right: 8px
                                font-size: 14px
                                color: rgb(240,20,20)
                            .old
                                text-decoration: line-through
                                font-size: 10px
                                color: rgb(147,153,159)
                        .cartcontrol-wrapper
                            position: absolute
                            right: 0
                            bottom: 12px
    </style>
    View Code

     cartcontrol组件(二)

     继续编写cartcontrol.vue组件,进一步实现购物车按钮效果

    <template>
      <div class="cartcontrol">
        <div class="cart-decrease icon-remove_circle_outline" v-show="food.count>0" @click="decreaseCart"></div>
        <div class="cart-count" v-show="food.count>0">{{food.count}}</div>
        <div class="cart-add icon-add_circle" @click="addCart"></div>
      </div>
    </template>
    
    <script>
      import Vue from 'vue';
      export default {
        name: 'v-cartcontrol',
        props: {
          food: {
            type: Object
          }
        },
        methods: {
          addCart(event) {
            if (!event._constructed) {
              return;
            }
            if (!this.food.count) {
              Vue.set(this.food, 'count', 1);
            } else {
              this.food.count++;
            }
          },
          decreaseCart(event) {
            if (!event._constructed) {
              return;
            }
            if (this.food.count) {
              this.food.count--;
            }
          }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .cartcontrol
        font-size: 0
        .cart-decrease, .cart-add
          font-size: 24px
          line-height: 24px
          padding: 6px
          color: rgb(0,160,220)
          display: inline-block
        .cart-count
          display: inline-block
          vertical-align: top
           12px
          padding-top: 6px
          line-height: 24px
          text-align: center
          font-size: 10px
          color: rgb(147,153,159)
        .cart-add
          display: inline-block
    </style>
    View Code

     cartcontrol组件(三)

    步骤一:编写cartcontrol.vue组件,增加食品增加和减少动画效果

    <template>
      <div class="cartcontrol">
        <transition name="move">
          <div class="cart-decrease" v-show="food.count>0" @click="decreaseCart">
            <span class="inner icon-remove_circle_outline"></span>
          </div>
        </transition>
        <div class="cart-count" v-show="food.count>0">{{food.count}}</div>
        <div class="cart-add icon-add_circle" @click="addCart"></div>
      </div>
    </template>
    
    <script>
      import Vue from 'vue';
      export default {
        name: 'v-cartcontrol',
        props: {
          food: {
            type: Object
          }
        },
        methods: {
          addCart(event) {
            if (!event._constructed) {
              return;
            }
            if (!this.food.count) {
              Vue.set(this.food, 'count', 1);
            } else {
              this.food.count++;
            }
          },
          decreaseCart(event) {
            if (!event._constructed) {
              return;
            }
            if (this.food.count) {
              this.food.count--;
            }
          }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .cartcontrol
        font-size: 0
        .cart-decrease
          padding: 6px
          color: rgb(0,160,220)
          display: inline-block
          opacity: 1
          transform: translate3d(0, 0, 0)
          .inner
            display: inline-block
            line-height: 24px
            font-size: 24px
            color: rgb(0, 160, 220)
            transition: all 0.4s linear
            transform: rotate(0)
          &.move-enter-active, &.move-leave-active
            transition: all 0.4s linear
          &.move-enter, &.move-leave-active
            opacity: 0
            transform: translate3d(24px, 0, 0)
            .inner
              transform: rotate(180deg)
        .cart-count
          display: inline-block
          vertical-align: top
           12px
          padding-top: 6px
          line-height: 24px
          text-align: center
          font-size: 10px
          color: rgb(147,153,159)
        .cart-add
          display: inline-block
          padding: 6px
          line-height: 24px
          font-size: 24px
          color: rgb(0, 160, 220)
    </style>
    View Code

    步骤二:编写goods.vue组件,将按钮和购物车区实现联动效果

    <template>
      <div class="goods">
        <div class="menu-wrapper" ref="menuWrapper">
          <ul>
            <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex===index}" @click="selectMenu(index,$event)">
              <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
              </span>
            </li>
          </ul>
        </div>
        <div class="foods-wrapper" ref="foodsWrapper">
          <ul>
            <li v-for="item in goods" class="food-list food-list-hook">
              <h1 class="title">{{item.name}}</h1>
              <ul>
                <li v-for="food in item.foods" class="food-item border-1px">
                  <div class="icon">
                    <img :src="food.icon" width="57" height="57" />
                  </div>
                  <div class="content">
                    <h2 class="name">{{food.name}}</h2>
                    <p class="desc">{{food.description}}</p>
                    <div class="extra">
                      <span class="count">月售{{food.sellCount}}份</span>
                      <span>好评率{{food.rating}}%</span>
                    </div>
                    <div class="price">
                      <span class="now">¥{{food.price}}</span>
                      <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                    </div>
                    <div class="cartcontrol-wrapper">
                      <cartcontrol :food="food"></cartcontrol>
                    </div>
                  </div>
                </li>
              </ul>
            </li>
          </ul>
        </div>
        <shopcart :select-foods="selectFoods" :delivery-price="seller.deliveryPrice" :min-price="seller.minPrice"></shopcart>
      </div>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import shopcart from '../../components/shopcart/shopcart';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
          seller: {
            type: Object
          }
        },
        data() {
          return {
            goods: [],
            listHeight: [],
            scrollY: 0
          };
        },
        computed: {
          currentIndex() {
            for (let i = 0; i < this.listHeight.length; i++) {
              let height1 = this.listHeight[i];
              let height2 = this.listHeight[i + 1];
              if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)) {
                return i;
              }
            }
            return 0;
          },
          selectFoods() {
            let foods = [];
            this.goods.forEach((good) => {
              good.foods.forEach((food) => {
                if (food.count) {
                  foods.push(food);
                }
              });
            });
            return foods;
          }
        },
        created() {
          this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
          this.$http.get('api/goods').then((response) => {
            response = response.body;
            if (response.error === ERR_OK) {
              this.goods = response.data;
            }
            this.goods = response.data;
            this.$nextTick(() => {
              this._initScroll();
              this._calculateHeight();
            });
          });
        },
        methods: {
          selectMenu(index, event) {
            if (!event._constructed) {
              return;
            }
            let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
            let el = foodList[index];
            this.foodsScroll.scrollToElement(el, 300);
            console.log(index);
          },
          _initScroll() {
            this.menuScroll = new BScroll(this.$refs.menuWrapper, {
              click: true
            });
    
            this.foodsScroll = new BScroll(this.$refs.foodsWrapper, {
              click: true,
              probeType: 3
            });
    
            this.foodsScroll.on("scroll", (pos) => {
              this.scrollY = Math.abs(Math.round(pos.y));
            });
          },
          _calculateHeight() {
            let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
            let height = 0;
            this.listHeight.push(height);
            for (let i = 0; i < foodList.length; i++) {
              let item = foodList[i];
              height += item.clientHeight;
              this.listHeight.push(height);
            }
          }
        },
        components: {
          shopcart,
          cartcontrol
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    &.current
                        position: relative
                        z-index: 10
                        margin-top: -1px
                        background: #ccc
                        font-weight: 700
                        .text
                            border-none()
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
                        .desc, .extra                    
                            line-height: 10px
                            font-size: 10px
                            color: rgb(147,153,159)
                        .desc
                            margin-bottom: 8px
                            line-height: 14px
                        .extra
                            .count
                                margin-right: 12px
                        .price
                            font-weight: 700
                            line-height: 24px
                            .now
                                margin-right: 8px
                                font-size: 14px
                                color: rgb(240,20,20)
                            .old
                                text-decoration: line-through
                                font-size: 10px
                                color: rgb(147,153,159)
                        .cartcontrol-wrapper
                            position: absolute
                            right: 0
                            bottom: 12px
    </style>
    View Code

    购物车小球动画实现(一)

    【注意】无法实现

    步骤一:编写goods.vue组件

    <template>
      <div class="goods">
        <div class="menu-wrapper" ref="menuWrapper">
          <ul>
            <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex===index}" @click="selectMenu(index,$event)">
              <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
              </span>
            </li>
          </ul>
        </div>
        <div class="foods-wrapper" ref="foodsWrapper">
          <ul>
            <li v-for="item in goods" class="food-list food-list-hook">
              <h1 class="title">{{item.name}}</h1>
              <ul>
                <li v-for="food in item.foods" class="food-item border-1px">
                  <div class="icon">
                    <img :src="food.icon" width="57" height="57" />
                  </div>
                  <div class="content">
                    <h2 class="name">{{food.name}}</h2>
                    <p class="desc">{{food.description}}</p>
                    <div class="extra">
                      <span class="count">月售{{food.sellCount}}份</span>
                      <span>好评率{{food.rating}}%</span>
                    </div>
                    <div class="price">
                      <span class="now">¥{{food.price}}</span>
                      <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                    </div>
                    <div class="cartcontrol-wrapper">
                      <cartcontrol :food="food"></cartcontrol>
                    </div>
                  </div>
                </li>
              </ul>
            </li>
          </ul>
        </div>
        <shopcart ref:shopcart :select-foods="selectFoods" :delivery-price="seller.deliveryPrice" :min-price="seller.minPrice"></shopcart>
      </div>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import shopcart from '../../components/shopcart/shopcart';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
          seller: {
            type: Object
          }
        },
        data() {
          return {
            goods: [],
            listHeight: [],
            scrollY: 0
          };
        },
        computed: {
          currentIndex() {
            for (let i = 0; i < this.listHeight.length; i++) {
              let height1 = this.listHeight[i];
              let height2 = this.listHeight[i + 1];
              if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)) {
                return i;
              }
            }
            return 0;
          },
          selectFoods() {
            let foods = [];
            this.goods.forEach((good) => {
              good.foods.forEach((food) => {
                if (food.count) {
                  foods.push(food);
                }
              });
            });
            return foods;
          }
        },
        created() {
          this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
          this.$http.get('api/goods').then((response) => {
            response = response.body;
            if (response.error === ERR_OK) {
              this.goods = response.data;
            }
            this.goods = response.data;
            this.$nextTick(() => {
              this._initScroll();
              this._calculateHeight();
            });
          });
        },
        methods: {
          selectMenu(index, event) {
            if (!event._constructed) {
              return;
            }
            let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
            let el = foodList[index];
            this.foodsScroll.scrollToElement(el, 300);
            console.log(index);
          },
          _drop(target) {
              //体验优化,异步执行下落动画
              this.$nextTick(() => {
                  this.$refs.shopcart.drop(target);
              });
          },
          _initScroll() {
            this.menuScroll = new BScroll(this.$refs.menuWrapper, {
              click: true
            });
    
            this.foodsScroll = new BScroll(this.$refs.foodsWrapper, {
              click: true,
              probeType: 3
            });
    
            this.foodsScroll.on("scroll", (pos) => {
              this.scrollY = Math.abs(Math.round(pos.y));
            });
          },
          _calculateHeight() {
            let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
            let height = 0;
            this.listHeight.push(height);
            for (let i = 0; i < foodList.length; i++) {
              let item = foodList[i];
              height += item.clientHeight;
              this.listHeight.push(height);
            }
          }
        },
        components: {
          shopcart,
          cartcontrol
        },
        events: {
            'cart.add'(target){
                this._drop(target);
            }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    &.current
                        position: relative
                        z-index: 10
                        margin-top: -1px
                        background: #ccc
                        font-weight: 700
                        .text
                            border-none()
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
                        .desc, .extra                    
                            line-height: 10px
                            font-size: 10px
                            color: rgb(147,153,159)
                        .desc
                            margin-bottom: 8px
                            line-height: 14px
                        .extra
                            .count
                                margin-right: 12px
                        .price
                            font-weight: 700
                            line-height: 24px
                            .now
                                margin-right: 8px
                                font-size: 14px
                                color: rgb(240,20,20)
                            .old
                                text-decoration: line-through
                                font-size: 10px
                                color: rgb(147,153,159)
                        .cartcontrol-wrapper
                            position: absolute
                            right: 0
                            bottom: 12px
    </style>
    View Code

    步骤二:编写carcontrol.vue组件

    <template>
      <div class="cartcontrol">
        <transition name="move">
          <div class="cart-decrease" v-show="food.count>0" @click.stop.prevent="decreaseCart">
            <span class="inner icon-remove_circle_outline"></span>
          </div>
        </transition>
        <div class="cart-count" v-show="food.count>0">{{food.count}}</div>
        <div class="cart-add icon-add_circle" @click.stop.prevent="addCart"></div>
      </div>
    </template>
    
    <script>
      import Vue from 'vue';
    
      export default {
        props: {
          food: {
            type: Object
          }
        },
        methods: {
          addCart(event) {
            if (!event._constructed) {
              return;
            }
            if (!this.food.count) {
              Vue.set(this.food, 'count', 1);
            } else {
              this.food.count++;
            }
            this.$dispatch('cart.add',event.target);
          },
          decreaseCart(event) {
            if (!event._constructed) {
              return;
            }
            if (this.food.count) {
              this.food.count--;
            }
          }
        }
      };
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      .cartcontrol
        font-size: 0
        .cart-decrease
          display: inline-block
          padding: 6px
          opacity: 1
          transform: translate3d(0, 0, 0)
          .inner
            display: inline-block
            line-height: 24px
            font-size: 24px
            color: rgb(0, 160, 220)
            transition: all 0.4s linear
            transform: rotate(0)
          &.move-enter-active, &.move-leave-active
            transition: all 0.4s linear
          &.move-enter, &.move-leave-active
            opacity: 0
            transform: translate3d(24px, 0, 0)
            .inner
              transform: rotate(180deg)
        .cart-count
          display: inline-block
          vertical-align: top
           12px
          padding-top: 6px
          line-height: 24px
          text-align: center
          font-size: 10px
          color: rgb(147, 153, 159)
        .cart-add
          display: inline-block
          padding: 6px
          line-height: 24px
          font-size: 24px
          color: rgb(0, 160, 220)
    </style>
    View Code

    步骤三:编写shoucart.vue组件

    <template>
      <div class="shopcart">
        <div class="content">
          <div class="content-left">
            <div class="logo-wrapper">
              <div class="logo" :class="{'highlight':totalCount>0}">
                <span class="icon-shopping_cart" :class="{'highlight':totalCount>0}"></span>
              </div>
              <div class="num" v-show="totalCount>0">{{totalCount}}</div>
            </div>
            <div class="price" :class="{'highlight':totalPrice>0}">¥{{totalPrice}}</div>
            <div class="desc">另需配送费¥{{deliveryPrice}}元</div>
          </div>
          <div class="content-right">
            <div class="pay" :class="payClass">
              {{payDesc}}
            </div>
          </div>
        </div>
        <div class="ball-container">
          <div v-for="ball in balls">
            <transition name="drop">
              <div class="ball" v-show="ball.show">
                <div class="inner inner-hook"></div>
              </div>
            </transition>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'v-shopcart',
      props: {
        selectFoods: {
          type: Array,
          default () {
            return [{
              price: 10,
              count: 5
            }];
          }
        },
        deliveryPrice: {
          type: Number,
          default: 0
        },
        minPrice: {
          type: Number,
          default: 0
        }
      },
      data() {
        return {
          balls: [{
              show: false
            },
            {
              show: false
            },
            {
              show: false
            },
            {
              show: false
            },
            {
              show: false
            }
          ],
          dropBall: []
        };
      },
      computed: {
        totalPrice() {
          let total = 0;
          this.selectFoods.forEach((food) => {
            total += food.price * food.count;
          });
          return total;
        },
        totalCount() {
          let count = 0;
          this.selectFoods.forEach((food) => {
            count += food.count;
          });
          return count;
        },
        payDesc() {
          if (this.totalPrice === 0) {
            return `¥${this.minPrice}元起送`;
          } else if (this.totalPrice < this.minPrice) {
            let diff = this.minPrice - this.totalPrice;
            return `还差¥${diff}元起送`;
          } else {
            return '去结算';
          }
        },
        payClass() {
          if (this.totalPrice < this.minPrice) {
            return 'not-enough';
          } else {
            return 'enough';
          }
        }
      },
      methods: {
        drop(el) {
          for (let i = 0; i < this.balls.length; i++) {
            let ball = this.balls[i];
            if (!ball.show) {
              ball.show = true;
              ball.el = el;
              this.dropBall.push(ball);
              return;
            }
          }
        }
      },
      transitions: {
          drop: {
              beforeDrop(el) {
            let count = this.balls.length;
            while (count--) {
              let ball = this.balls[count];
              if (ball.show) {
                let rect = ball.el.getBoundingClientRect();
                let x = rect.left - 32;
                let y = -(window.innerHeight - rect.top - 22);
                el.style.display = '';
                el.style.webkitTransform = `translate3d(0,${y}px,0)`;
                el.style.transform = `translate3d(0,${y}px,0)`;
                let inner = el.getElementsByClassName('inner-hook')[0];
                inner.style.webkitTransform = `translate3d(${x}px,0,0)`;
                inner.style.transform = `translate3d(${x}px,0,0)`;
              }
            }
          },
          enter(el) {
              /* eslint-disable no-unused-vars */
            let rf = el.offsetHeight;
            this.$nextTick(() => {
              el.style.webkitTransform = 'translate3d(0,0,0)';
              el.style.transform = 'translate3d(0,0,0)';
              let inner = el.getElementsByClassName('inner-hook')[0];
              inner.style.webkitTransform = 'translate3d(0,0,0)';
              inner.style.transform = 'translate3d(0,0,0)';
              el.addEventListener('transitionend', done);
            });
          },
          afterEnter(el) {
              let ball = this.dropBalls.shift();
            if (ball) {
              ball.show = false;
              el.style.display = 'none';
            }
          }
          }
      }
    }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .shopcart
        position: fixed
        left: 0
        bottom: 0
        z-index: 50
         100%
        height: 48px
        .content
            display: flex
            background: #141d27
            font-size: 0
            .content-left
                flex: 1
                .logo-wrapper
                    display: inline-block
                    position: relative
                    top:-10px
                    margin: 0 12px
                    padding: 6px
                     56px
                    height: 56px
                    vertical-align: top
                    box-sizing: border-box
                    border-radius: 50%
                    background: #141d27
                    .logo
                         100%
                        height: 100%
                        border-radius: 50%
                        text-align: center
                        background: #2b343c
                        &.highlight
                            background: rgb(0,160,220)
                        .icon-shopping_cart
                            font-size: 24px
                            line-height: 44px
                            color: #80858a
                            &.highlight
                                color: #fff
                    .num
                        position: absolute
                        top: 0px
                        right: 0px
                         24px
                        height: 16px
                        line-height: 16px
                        text-align: center
                        border-radius: 16px
                        font-size: 9px
                        font-weight: 700
                        color: #fff
                        background: rgb(240,20,20)
                        box-shadow: 0 4px 8px 0 rgba(0,0,0,0.4)
                .price
                    display: inline-block
                    vertical-align: top
                    margin-top: 12px
                    line-height: 24px
                    padding-right: 12px
                    box-sizing: border-box
                    border-right: 1px solid rgba(255,255,255,0.1)
                    font-size: 16px
                    font-weight: 700
                    color: rgba(255,255,255,0.4)
                    &.highlight
                        color: #fff
                .desc
                    display: inline-block
                    vertical-align: top
                    line-height: 24px
                    margin: 12px 0 0 12px
                    font-size: 10px
                    color: rgba(255,255,255,0.4)
            .content-right
                flex: 0 0 105px
                 105px
                .pay
                    height: 48px
                    line-height: 48px
                    text-align: center
                    font-size: 12px
                    color: rgba(255,255,255,0.4)
                    font-weight: 700
                    background: #2b333b
                    &.not-enough
                        background: #2b333b
                    &.enough
                        background: #00b43c
                        color: #fff
        .ball-container
          .ball
            position: fixed
            left: 32px
            bottom: 22px
            z-index: 200
            transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41)
            .inner
               16px
              height: 16px
              border-radius: 50%
              background: rgb(0, 160, 220)
              transition: all 0.4s linear
    </style>
    View Code

    购物车详情页(一、二)

    编写shopcart.vue组件,实现初步效果

    <template>
      <div class="shopcart">
        <div class="content" @click="toggleList">
          <div class="content-left">
            <div class="logo-wrapper">
              <div class="logo" :class="{'highlight':totalCount>0}">
                <span class="icon-shopping_cart" :class="{'highlight':totalCount>0}"></span>
              </div>
              <div class="num" v-show="totalCount>0">{{totalCount}}</div>
            </div>
            <div class="price" :class="{'highlight':totalPrice>0}">¥{{totalPrice}}</div>
            <div class="desc">另需配送费¥{{deliveryPrice}}元</div>
          </div>
          <div class="content-right">
            <div class="pay" :class="payClass">
              {{payDesc}}
            </div>
          </div>
        </div>
        <div class="ball-container">
          <div v-for="ball in balls">
            <transition name="drop">
              <div class="ball" v-show="ball.show">
                <div class="inner inner-hook"></div>
              </div>
            </transition>
          </div>
        </div>
        <transition name="fold">
          <div class="shopcart-list" v-show="listShow">
            <div class="list-header">
              <h1 class="title">购物车</h1>
              <span class="empty">清空</span>
            </div>
            <div class="list-content">
              <ul>
                <li class="food" v-for="food in selectFoods">
                  <span class="name">{{food.name}}</span>
                  <div class="price">
                    <span>¥{{food.price*food.count}}</span>
                  </div>
                  <div class="cartcontrol-wrapper">
                    <cartcontrol :food="food"></cartcontrol>
                  </div>
                </li>
              </ul>
            </div>
          </div>
        </transition>
      </div>
    </template>
    
    <script>
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      export default {
        name: 'v-shopcart',
        props: {
          selectFoods: {
            type: Array,
            default () {
              return [{
                price: 10,
                count: 5
              }];
            }
          },
          deliveryPrice: {
            type: Number,
            default: 0
          },
          minPrice: {
            type: Number,
            default: 0
          }
        },
        data() {
          return {
            balls: [{
                show: false
              },
              {
                show: false
              },
              {
                show: false
              },
              {
                show: false
              },
              {
                show: false
              }
            ],
            dropBall: [],
            fold: true
          };
        },
        computed: {
          totalPrice() {
            let total = 0;
            this.selectFoods.forEach((food) => {
              total += food.price * food.count;
            });
            return total;
          },
          totalCount() {
            let count = 0;
            this.selectFoods.forEach((food) => {
              count += food.count;
            });
            return count;
          },
          payDesc() {
            if (this.totalPrice === 0) {
              return `¥${this.minPrice}元起送`;
            } else if (this.totalPrice < this.minPrice) {
              let diff = this.minPrice - this.totalPrice;
              return `还差¥${diff}元起送`;
            } else {
              return '去结算';
            }
          },
          payClass() {
            if (this.totalPrice < this.minPrice) {
              return 'not-enough';
            } else {
              return 'enough';
            }
          },
          listShow() {
            if (!this.totalCount) {
              this.fold = true;
              return false;
            }
            let show = !this.fold;
            return show;
          }
        },
        methods: {
          drop(el) {
            for (let i = 0; i < this.balls.length; i++) {
              let ball = this.balls[i];
              if (!ball.show) {
                ball.show = true;
                ball.el = el;
                this.dropBall.push(ball);
                return;
              }
            }
          },
          toggleList() {
            if (!this.totalCount) {
              return;
            }
            this.fold = !this.fold;
          }
        },
        transitions: {
          drop: {
            beforeDrop(el) {
              let count = this.balls.length;
              while (count--) {
                let ball = this.balls[count];
                if (ball.show) {
                  let rect = ball.el.getBoundingClientRect();
                  let x = rect.left - 32;
                  let y = -(window.innerHeight - rect.top - 22);
                  el.style.display = '';
                  el.style.webkitTransform = `translate3d(0,${y}px,0)`;
                  el.style.transform = `translate3d(0,${y}px,0)`;
                  let inner = el.getElementsByClassName('inner-hook')[0];
                  inner.style.webkitTransform = `translate3d(${x}px,0,0)`;
                  inner.style.transform = `translate3d(${x}px,0,0)`;
                }
              }
            },
            enter(el) {
              /* eslint-disable no-unused-vars */
              let rf = el.offsetHeight;
              this.$nextTick(() => {
                el.style.webkitTransform = 'translate3d(0,0,0)';
                el.style.transform = 'translate3d(0,0,0)';
                let inner = el.getElementsByClassName('inner-hook')[0];
                inner.style.webkitTransform = 'translate3d(0,0,0)';
                inner.style.transform = 'translate3d(0,0,0)';
                el.addEventListener('transitionend', done);
              });
            },
            afterEnter(el) {
              let ball = this.dropBalls.shift();
              if (ball) {
                ball.show = false;
                el.style.display = 'none';
              }
            }
          }
        },
        components: {
          cartcontrol
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin";
      .shopcart
        position: fixed
        left: 0
        bottom: 0
        z-index: 50
         100%
        height: 48px
        .content
            display: flex
            background: #141d27
            font-size: 0
            .content-left
                flex: 1
                .logo-wrapper
                    display: inline-block
                    position: relative
                    top:-10px
                    margin: 0 12px
                    padding: 6px
                     56px
                    height: 56px
                    vertical-align: top
                    box-sizing: border-box
                    border-radius: 50%
                    background: #141d27
                    .logo
                         100%
                        height: 100%
                        border-radius: 50%
                        text-align: center
                        background: #2b343c
                        &.highlight
                            background: rgb(0,160,220)
                        .icon-shopping_cart
                            font-size: 24px
                            line-height: 44px
                            color: #80858a
                            &.highlight
                                color: #fff
                    .num
                        position: absolute
                        top: 0px
                        right: 0px
                         24px
                        height: 16px
                        line-height: 16px
                        text-align: center
                        border-radius: 16px
                        font-size: 9px
                        font-weight: 700
                        color: #fff
                        background: rgb(240,20,20)
                        box-shadow: 0 4px 8px 0 rgba(0,0,0,0.4)
                .price
                    display: inline-block
                    vertical-align: top
                    margin-top: 12px
                    line-height: 24px
                    padding-right: 12px
                    box-sizing: border-box
                    border-right: 1px solid rgba(255,255,255,0.1)
                    font-size: 16px
                    font-weight: 700
                    color: rgba(255,255,255,0.4)
                    &.highlight
                        color: #fff
                .desc
                    display: inline-block
                    vertical-align: top
                    line-height: 24px
                    margin: 12px 0 0 12px
                    font-size: 10px
                    color: rgba(255,255,255,0.4)
            .content-right
                flex: 0 0 105px
                 105px
                .pay
                    height: 48px
                    line-height: 48px
                    text-align: center
                    font-size: 12px
                    color: rgba(255,255,255,0.4)
                    font-weight: 700
                    background: #2b333b
                    &.not-enough
                        background: #2b333b
                    &.enough
                        background: #00b43c
                        color: #fff
        .ball-container
          .ball
            position: fixed
            left: 32px
            bottom: 22px
            z-index: 200
            transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41)
            .inner
               16px
              height: 16px
              border-radius: 50%
              background: rgb(0, 160, 220)
              transition: all 0.4s linear
        .shopcart-list
          position: absolute
          left: 0
          top: 0
          z-index: -1
           100%
          transform: translate3d(0, -100%, 0)
          &.fold-enter-active, &.fold-leave-active
            transition: all 0.5s
          &.fold-enter, &.fold-leave-active
            transform: translate3d(0, 0, 0)
          .list-header
            height: 40px
            line-height: 40px
            padding: 0 18px
            background: #f3f5f7
            border-bottom: 1px solid rgba(7, 17, 27, 0.1)
            .title
              float: left
              font-size: 14px
              color: rgb(7, 17, 27)
            .empty
              float: right
              font-size: 12px
              color: rgb(0, 160, 220)
          .list-content
            padding: 0 18px
            max-height: 217px
            overflow: hidden
            background: #fff
            .food
              position: relative
              padding: 12px 0
              box-sizing: border-box
              border-1px(rgba(7, 17, 27, 0.1))
            .name
              line-height: 24px
              font-size: 14px
              color: rgb(7, 17, 27)
            .price
              position: absolute
              right: 90px
              bottom: 12px
              line-height: 24px
              font-size: 14px
              font-weight: 700
              color: rgb(240, 20, 20)
            .cartcontrol-wrapper
              position: absolute
              right: 0
              bottom: 6px
    </style>
    View Code

    购物车详情页(三)

    继续编写shopcart.vue组件,实现增加和减少功能

    <template>
      <div class="shopcart">
        <div class="content" @click="toggleList">
          <div class="content-left">
            <div class="logo-wrapper">
              <div class="logo" :class="{'highlight':totalCount>0}">
                <span class="icon-shopping_cart" :class="{'highlight':totalCount>0}"></span>
              </div>
              <div class="num" v-show="totalCount>0">{{totalCount}}</div>
            </div>
            <div class="price" :class="{'highlight':totalPrice>0}">¥{{totalPrice}}</div>
            <div class="desc">另需配送费¥{{deliveryPrice}}元</div>
          </div>
          <div class="content-right">
            <div class="pay" :class="payClass">
              {{payDesc}}
            </div>
          </div>
        </div>
        <div class="ball-container">
          <div v-for="ball in balls">
            <transition name="drop">
              <div class="ball" v-show="ball.show">
                <div class="inner inner-hook"></div>
              </div>
            </transition>
          </div>
        </div>
        <transition name="fold">
          <div class="shopcart-list" v-show="listShow">
            <div class="list-header">
              <h1 class="title">购物车</h1>
              <span class="empty">清空</span>
            </div>
            <div class="list-content" ref="listContent">
              <ul>
                <li class="food" v-for="food in selectFoods">
                  <span class="name">{{food.name}}</span>
                  <div class="price">
                    <span>¥{{food.price*food.count}}</span>
                  </div>
                  <div class="cartcontrol-wrapper">
                    <cartcontrol :food="food"></cartcontrol>
                  </div>
                </li>
              </ul>
            </div>
          </div>
        </transition>
      </div>
    </template>
    
    <script>
        import BScroll from 'better-scroll';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      export default {
        name: 'v-shopcart',
        props: {
          selectFoods: {
            type: Array,
            default () {
              return [{
                price: 10,
                count: 5
              }];
            }
          },
          deliveryPrice: {
            type: Number,
            default: 0
          },
          minPrice: {
            type: Number,
            default: 0
          }
        },
        data() {
          return {
            balls: [{
                show: false
              },
              {
                show: false
              },
              {
                show: false
              },
              {
                show: false
              },
              {
                show: false
              }
            ],
            dropBall: [],
            fold: true
          };
        },
        computed: {
          totalPrice() {
            let total = 0;
            this.selectFoods.forEach((food) => {
              total += food.price * food.count;
            });
            return total;
          },
          totalCount() {
            let count = 0;
            this.selectFoods.forEach((food) => {
              count += food.count;
            });
            return count;
          },
          payDesc() {
            if (this.totalPrice === 0) {
              return `¥${this.minPrice}元起送`;
            } else if (this.totalPrice < this.minPrice) {
              let diff = this.minPrice - this.totalPrice;
              return `还差¥${diff}元起送`;
            } else {
              return '去结算';
            }
          },
          payClass() {
            if (this.totalPrice < this.minPrice) {
              return 'not-enough';
            } else {
              return 'enough';
            }
          },
          listShow() {
            if (!this.totalCount) {
              this.fold = true;
              return false;
            }
            let show = !this.fold;
            if (show) {
              this.$nextTick(() => {
                if (!this.scroll) {
                  this.scroll = new BScroll(this.$refs.listContent, {
                    click: true
                  });
                } else {
                  this.scroll.refresh();
                }
              });
            }
            return show;
          }
        },
        methods: {
          drop(el) {
            for (let i = 0; i < this.balls.length; i++) {
              let ball = this.balls[i];
              if (!ball.show) {
                ball.show = true;
                ball.el = el;
                this.dropBall.push(ball);
                return;
              }
            }
          },
          toggleList() {
            if (!this.totalCount) {
              return;
            }
            this.fold = !this.fold;
          }
        },
        transitions: {
          drop: {
            beforeDrop(el) {
              let count = this.balls.length;
              while (count--) {
                let ball = this.balls[count];
                if (ball.show) {
                  let rect = ball.el.getBoundingClientRect();
                  let x = rect.left - 32;
                  let y = -(window.innerHeight - rect.top - 22);
                  el.style.display = '';
                  el.style.webkitTransform = `translate3d(0,${y}px,0)`;
                  el.style.transform = `translate3d(0,${y}px,0)`;
                  let inner = el.getElementsByClassName('inner-hook')[0];
                  inner.style.webkitTransform = `translate3d(${x}px,0,0)`;
                  inner.style.transform = `translate3d(${x}px,0,0)`;
                }
              }
            },
            enter(el) {
              /* eslint-disable no-unused-vars */
              let rf = el.offsetHeight;
              this.$nextTick(() => {
                el.style.webkitTransform = 'translate3d(0,0,0)';
                el.style.transform = 'translate3d(0,0,0)';
                let inner = el.getElementsByClassName('inner-hook')[0];
                inner.style.webkitTransform = 'translate3d(0,0,0)';
                inner.style.transform = 'translate3d(0,0,0)';
                el.addEventListener('transitionend', done);
              });
            },
            afterEnter(el) {
              let ball = this.dropBalls.shift();
              if (ball) {
                ball.show = false;
                el.style.display = 'none';
              }
            }
          }
        },
        components: {
          cartcontrol
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin";
      .shopcart
        position: fixed
        left: 0
        bottom: 0
        z-index: 50
         100%
        height: 48px
        .content
            display: flex
            background: #141d27
            font-size: 0
            .content-left
                flex: 1
                .logo-wrapper
                    display: inline-block
                    position: relative
                    top:-10px
                    margin: 0 12px
                    padding: 6px
                     56px
                    height: 56px
                    vertical-align: top
                    box-sizing: border-box
                    border-radius: 50%
                    background: #141d27
                    .logo
                         100%
                        height: 100%
                        border-radius: 50%
                        text-align: center
                        background: #2b343c
                        &.highlight
                            background: rgb(0,160,220)
                        .icon-shopping_cart
                            font-size: 24px
                            line-height: 44px
                            color: #80858a
                            &.highlight
                                color: #fff
                    .num
                        position: absolute
                        top: 0px
                        right: 0px
                         24px
                        height: 16px
                        line-height: 16px
                        text-align: center
                        border-radius: 16px
                        font-size: 9px
                        font-weight: 700
                        color: #fff
                        background: rgb(240,20,20)
                        box-shadow: 0 4px 8px 0 rgba(0,0,0,0.4)
                .price
                    display: inline-block
                    vertical-align: top
                    margin-top: 12px
                    line-height: 24px
                    padding-right: 12px
                    box-sizing: border-box
                    border-right: 1px solid rgba(255,255,255,0.1)
                    font-size: 16px
                    font-weight: 700
                    color: rgba(255,255,255,0.4)
                    &.highlight
                        color: #fff
                .desc
                    display: inline-block
                    vertical-align: top
                    line-height: 24px
                    margin: 12px 0 0 12px
                    font-size: 10px
                    color: rgba(255,255,255,0.4)
            .content-right
                flex: 0 0 105px
                 105px
                .pay
                    height: 48px
                    line-height: 48px
                    text-align: center
                    font-size: 12px
                    color: rgba(255,255,255,0.4)
                    font-weight: 700
                    background: #2b333b
                    &.not-enough
                        background: #2b333b
                    &.enough
                        background: #00b43c
                        color: #fff
        .ball-container
          .ball
            position: fixed
            left: 32px
            bottom: 22px
            z-index: 200
            transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41)
            .inner
               16px
              height: 16px
              border-radius: 50%
              background: rgb(0, 160, 220)
              transition: all 0.4s linear
        .shopcart-list
          position: absolute
          left: 0
          top: 0
          z-index: -1
           100%
          transform: translate3d(0, -100%, 0)
          &.fold-enter-active, &.fold-leave-active
            transition: all 0.5s
          &.fold-enter, &.fold-leave-active
            transform: translate3d(0, 0, 0)
          .list-header
            height: 40px
            line-height: 40px
            padding: 0 18px
            background: #f3f5f7
            border-bottom: 1px solid rgba(7, 17, 27, 0.1)
            .title
              float: left
              font-size: 14px
              color: rgb(7, 17, 27)
            .empty
              float: right
              font-size: 12px
              color: rgb(0, 160, 220)
          .list-content
            padding: 0 18px
            max-height: 217px
            overflow: hidden
            background: #fff
            .food
              position: relative
              padding: 12px 0
              box-sizing: border-box
              border-1px(rgba(7, 17, 27, 0.1))
            .name
              line-height: 24px
              font-size: 14px
              color: rgb(7, 17, 27)
            .price
              position: absolute
              right: 90px
              bottom: 12px
              line-height: 24px
              font-size: 14px
              font-weight: 700
              color: rgb(240, 20, 20)
            .cartcontrol-wrapper
              position: absolute
              right: 0
              bottom: 6px
    </style>
    View Code

    购物车详情页(四)

    继续编写shopcart.vue组件,实现蒙层效果

    <template>
      <div class="shopcart">
        <div class="content" @click="toggleList">
          <div class="content-left">
            <div class="logo-wrapper">
              <div class="logo" :class="{'highlight':totalCount>0}">
                <span class="icon-shopping_cart" :class="{'highlight':totalCount>0}"></span>
              </div>
              <div class="num" v-show="totalCount>0">{{totalCount}}</div>
            </div>
            <div class="price" :class="{'highlight':totalPrice>0}">¥{{totalPrice}}</div>
            <div class="desc">另需配送费¥{{deliveryPrice}}元</div>
          </div>
          <!--@click.stop.prevent阻止冒泡行为-->
          <div class="content-right" @click.stop.prevent="pay">
            <div class="pay" :class="payClass">
              {{payDesc}}
            </div>
          </div>
        </div>
        <div class="ball-container">
          <div v-for="ball in balls">
            <transition name="drop">
              <div class="ball" v-show="ball.show">
                <div class="inner inner-hook"></div>
              </div>
            </transition>
          </div>
        </div>
        <transition name="fold">
          <div class="shopcart-list" v-show="listShow">
            <div class="list-header">
              <h1 class="title">购物车</h1>
              <span class="empty" @click="empty">清空</span>
            </div>
            <div class="list-content" ref="listContent">
              <ul>
                <li class="food" v-for="food in selectFoods">
                  <span class="name">{{food.name}}</span>
                  <div class="price">
                    <span>¥{{food.price*food.count}}</span>
                  </div>
                  <div class="cartcontrol-wrapper">
                    <cartcontrol :food="food"></cartcontrol>
                  </div>
                </li>
              </ul>
            </div>
          </div>
        </transition>
        <transition name="fade">
          <div class="list-mask" @click="hideList" v-show="listShow"></div>
        </transition>
      </div>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      export default {
        name: 'v-shopcart',
        props: {
          selectFoods: {
            type: Array,
            default () {
              return [{
                price: 10,
                count: 5
              }];
            }
          },
          deliveryPrice: {
            type: Number,
            default: 0
          },
          minPrice: {
            type: Number,
            default: 0
          }
        },
        data() {
          return {
            balls: [{
                show: false
              },
              {
                show: false
              },
              {
                show: false
              },
              {
                show: false
              },
              {
                show: false
              }
            ],
            dropBall: [],
            fold: true
          };
        },
        computed: {
          totalPrice() {
            let total = 0;
            this.selectFoods.forEach((food) => {
              total += food.price * food.count;
            });
            return total;
          },
          totalCount() {
            let count = 0;
            this.selectFoods.forEach((food) => {
              count += food.count;
            });
            return count;
          },
          payDesc() {
            if (this.totalPrice === 0) {
              return `¥${this.minPrice}元起送`;
            } else if (this.totalPrice < this.minPrice) {
              let diff = this.minPrice - this.totalPrice;
              return `还差¥${diff}元起送`;
            } else {
              return '去结算';
            }
          },
          payClass() {
            if (this.totalPrice < this.minPrice) {
              return 'not-enough';
            } else {
              return 'enough';
            }
          },
          listShow() {
            if (!this.totalCount) {
              this.fold = true;
              return false;
            }
            let show = !this.fold;
            if (show) {
              this.$nextTick(() => {
                if (!this.scroll) {
                  this.scroll = new BScroll(this.$refs.listContent, {
                    click: true
                  });
                } else {
                  this.scroll.refresh();
                }
              });
            }
            return show;
          }
        },
        methods: {
          drop(el) {
            for (let i = 0; i < this.balls.length; i++) {
              let ball = this.balls[i];
              if (!ball.show) {
                ball.show = true;
                ball.el = el;
                this.dropBall.push(ball);
                return;
              }
            }
          },
          toggleList() {
            if (!this.totalCount) {
              return;
            }
            this.fold = !this.fold;
          },
          hideList() {
            this.fold = true;
          },
          empty() {
            this.selectFoods.forEach((food) => {
              food.count = 0;
            });
          },
          pay() {
            if (this.totalPrice < this.minPrice) {
              return;
            }
            window.alert(`支付${this.totalPrice}元`);
          }
        },
        transitions: {
          drop: {
            beforeDrop(el) {
              let count = this.balls.length;
              while (count--) {
                let ball = this.balls[count];
                if (ball.show) {
                  let rect = ball.el.getBoundingClientRect();
                  let x = rect.left - 32;
                  let y = -(window.innerHeight - rect.top - 22);
                  el.style.display = '';
                  el.style.webkitTransform = `translate3d(0,${y}px,0)`;
                  el.style.transform = `translate3d(0,${y}px,0)`;
                  let inner = el.getElementsByClassName('inner-hook')[0];
                  inner.style.webkitTransform = `translate3d(${x}px,0,0)`;
                  inner.style.transform = `translate3d(${x}px,0,0)`;
                }
              }
            },
            enter(el) {
              /* eslint-disable no-unused-vars */
              let rf = el.offsetHeight;
              this.$nextTick(() => {
                el.style.webkitTransform = 'translate3d(0,0,0)';
                el.style.transform = 'translate3d(0,0,0)';
                let inner = el.getElementsByClassName('inner-hook')[0];
                inner.style.webkitTransform = 'translate3d(0,0,0)';
                inner.style.transform = 'translate3d(0,0,0)';
                el.addEventListener('transitionend', done);
              });
            },
            afterEnter(el) {
              let ball = this.dropBalls.shift();
              if (ball) {
                ball.show = false;
                el.style.display = 'none';
              }
            }
          }
        },
        components: {
          cartcontrol
        }
      }
    </script>
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin";
      .shopcart
        position: fixed
        left: 0
        bottom: 0
        z-index: 50
         100%
        height: 48px
        .content
            display: flex
            background: #141d27
            font-size: 0
            .content-left
                flex: 1
                .logo-wrapper
                    display: inline-block
                    position: relative
                    top:-10px
                    margin: 0 12px
                    padding: 6px
                     56px
                    height: 56px
                    vertical-align: top
                    box-sizing: border-box
                    border-radius: 50%
                    background: #141d27
                    .logo
                         100%
                        height: 100%
                        border-radius: 50%
                        text-align: center
                        background: #2b343c
                        &.highlight
                            background: rgb(0,160,220)
                        .icon-shopping_cart
                            font-size: 24px
                            line-height: 44px
                            color: #80858a
                            &.highlight
                                color: #fff
                    .num
                        position: absolute
                        top: 0px
                        right: 0px
                         24px
                        height: 16px
                        line-height: 16px
                        text-align: center
                        border-radius: 16px
                        font-size: 9px
                        font-weight: 700
                        color: #fff
                        background: rgb(240,20,20)
                        box-shadow: 0 4px 8px 0 rgba(0,0,0,0.4)
                .price
                    display: inline-block
                    vertical-align: top
                    margin-top: 12px
                    line-height: 24px
                    padding-right: 12px
                    box-sizing: border-box
                    border-right: 1px solid rgba(255,255,255,0.1)
                    font-size: 16px
                    font-weight: 700
                    color: rgba(255,255,255,0.4)
                    &.highlight
                        color: #fff
                .desc
                    display: inline-block
                    vertical-align: top
                    line-height: 24px
                    margin: 12px 0 0 12px
                    font-size: 10px
                    color: rgba(255,255,255,0.4)
            .content-right
                flex: 0 0 105px
                 105px
                .pay
                    height: 48px
                    line-height: 48px
                    text-align: center
                    font-size: 12px
                    color: rgba(255,255,255,0.4)
                    font-weight: 700
                    background: #2b333b
                    &.not-enough
                        background: #2b333b
                    &.enough
                        background: #00b43c
                        color: #fff
        .ball-container
          .ball
            position: fixed
            left: 32px
            bottom: 22px
            z-index: 200
            transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41)
            .inner
               16px
              height: 16px
              border-radius: 50%
              background: rgb(0, 160, 220)
              transition: all 0.4s linear
        .shopcart-list
          position: absolute
          left: 0
          top: 0
          z-index: 50
           100%
          transform: translate3d(0, -100%, 0)
          &.fold-enter-active, &.fold-leave-active
            transition: all 0.5s
          &.fold-enter, &.fold-leave-active
            transform: translate3d(0, 0, 0)
          .list-header
            height: 40px
            line-height: 40px
            padding: 0 18px
            background: #f3f5f7
            border-bottom: 1px solid rgba(7, 17, 27, 0.1)
            .title
              float: left
              font-size: 14px
              color: rgb(7, 17, 27)
            .empty
              float: right
              font-size: 12px
              color: rgb(0, 160, 220)
          .list-content
            padding: 0 18px
            max-height: 217px
            overflow: hidden
            background: #fff
            .food
              position: relative
              padding: 12px 0
              box-sizing: border-box
              border-1px(rgba(7, 17, 27, 0.1))
            .name
              line-height: 24px
              font-size: 14px
              color: rgb(7, 17, 27)
            .price
              position: absolute
              right: 90px
              bottom: 12px
              line-height: 24px
              font-size: 14px
              font-weight: 700
              color: rgb(240, 20, 20)
            .cartcontrol-wrapper
              position: absolute
              right: 0
              bottom: 6px
        .list-mask
          position: fixed
          top: 0
          left: 0
           100%
          height: 100%
          z-index: 40
          backdrop-filter: blur(10px)
          opacity: 1
          background: rgba(7, 17, 27, 0.6)
          &.fade-enter-active, &.fade-leave-active
            transition: all 0.5s
          &.fade-enter, &.fade-leave-active
            opacity: 0
            background: rgba(7, 17, 27, 0)
    </style>
    View Code

    商品详情页实现(一)

    步骤一:编写goods.vue组件

    <template>
      <div>
        <div class="goods">
          <div class="menu-wrapper" ref="menuWrapper">
            <ul>
              <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex===index}" @click="selectMenu(index,$event)">
                <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
                </span>
              </li>
            </ul>
          </div>
          <div class="foods-wrapper" ref="foodsWrapper">
            <ul>
              <li v-for="item in goods" class="food-list food-list-hook">
                <h1 class="title">{{item.name}}</h1>
                <ul>
                  <li @click="selectFood(food,$event)" v-for="food in item.foods" class="food-item border-1px">
                    <div class="icon">
                      <img :src="food.icon" width="57" height="57" />
                    </div>
                    <div class="content">
                      <h2 class="name">{{food.name}}</h2>
                      <p class="desc">{{food.description}}</p>
                      <div class="extra">
                        <span class="count">月售{{food.sellCount}}份</span>
                        <span>好评率{{food.rating}}%</span>
                      </div>
                      <div class="price">
                        <span class="now">¥{{food.price}}</span>
                        <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                      </div>
                      <div class="cartcontrol-wrapper">
                        <cartcontrol :food="food"></cartcontrol>
                      </div>
                    </div>
                  </li>
                </ul>
              </li>
            </ul>
          </div>
          <shopcart ref:shopcart :select-foods="selectFoods" :delivery-price="seller.deliveryPrice" :min-price="seller.minPrice"></shopcart>
        </div>
        <food :food="selectedFood"></food>
      </div>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import shopcart from '../../components/shopcart/shopcart';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      import food from '../../components/food/food';
      const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
          seller: {
            type: Object
          }
        },
        data() {
          return {
            goods: [],
            listHeight: [],
            scrollY: 0,
            selectedFood: {}
          };
        },
        computed: {
          currentIndex() {
            for (let i = 0; i < this.listHeight.length; i++) {
              let height1 = this.listHeight[i];
              let height2 = this.listHeight[i + 1];
              if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)) {
                return i;
              }
            }
            return 0;
          },
          selectFoods() {
            let foods = [];
            this.goods.forEach((good) => {
              good.foods.forEach((food) => {
                if (food.count) {
                  foods.push(food);
                }
              });
            });
            return foods;
          }
        },
        created() {
          this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
          this.$http.get('api/goods').then((response) => {
            response = response.body;
            if (response.error === ERR_OK) {
              this.goods = response.data;
            }
            this.goods = response.data;
            this.$nextTick(() => {
              this._initScroll();
              this._calculateHeight();
            });
          });
        },
        methods: {
          selectMenu(index, event) {
            if (!event._constructed) {
              return;
            }
            let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
            let el = foodList[index];
            this.foodsScroll.scrollToElement(el, 300);
            console.log(index);
          },
          selectFood(food, event) {
            if (!event._constructed) {
              return;
            }
            this.selectedFood = food;
          },
          _drop(target) {
            //体验优化,异步执行下落动画
            this.$nextTick(() => {
              this.$refs.shopcart.drop(target);
            });
          },
          _initScroll() {
            this.menuScroll = new BScroll(this.$refs.menuWrapper, {
              click: true
            });
    
            this.foodsScroll = new BScroll(this.$refs.foodsWrapper, {
              click: true,
              probeType: 3
            });
    
            this.foodsScroll.on("scroll", (pos) => {
              this.scrollY = Math.abs(Math.round(pos.y));
            });
          },
          _calculateHeight() {
            let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
            let height = 0;
            this.listHeight.push(height);
            for (let i = 0; i < foodList.length; i++) {
              let item = foodList[i];
              height += item.clientHeight;
              this.listHeight.push(height);
            }
          }
        },
        components: {
          shopcart,
          cartcontrol,
          food
        },
        events: {
          'cart.add' (target) {
            this._drop(target);
          }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    &.current
                        position: relative
                        z-index: 10
                        margin-top: -1px
                        background: #ccc
                        font-weight: 700
                        .text
                            border-none()
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
                        .desc, .extra                    
                            line-height: 10px
                            font-size: 10px
                            color: rgb(147,153,159)
                        .desc
                            margin-bottom: 8px
                            line-height: 14px
                        .extra
                            .count
                                margin-right: 12px
                        .price
                            font-weight: 700
                            line-height: 24px
                            .now
                                margin-right: 8px
                                font-size: 14px
                                color: rgb(240,20,20)
                            .old
                                text-decoration: line-through
                                font-size: 10px
                                color: rgb(147,153,159)
                        .cartcontrol-wrapper
                            position: absolute
                            right: 0
                            bottom: 12px
    </style>
    View Code

    步骤二:新建food.vuez组件

    <template>
      <div v-show="showFlag" class="food"></div>
    </template>
    
    <script>
      export default {
        name: 'v-food',
        props: {
          food: {
            type: Object
          }
        },
        data() {
          return {
            showFlag: false
          };
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        
    </style>
    View Code

    商品详情实现(二)

    步骤一:编写goods.vue组件

    <template>
      <div>
        <div class="goods">
          <div class="menu-wrapper" ref="menuWrapper">
            <ul>
              <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex===index}" @click="selectMenu(index,$event)">
                <span class="text border-1px">
                            <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span> {{item.name}}
                </span>
              </li>
            </ul>
          </div>
          <div class="foods-wrapper" ref="foodsWrapper">
            <ul>
              <li v-for="item in goods" class="food-list food-list-hook">
                <h1 class="title">{{item.name}}</h1>
                <ul>
                  <li @click="selectFood(food,$event)" v-for="food in item.foods" class="food-item border-1px">
                    <div class="icon">
                      <img :src="food.icon" width="57" height="57" />
                    </div>
                    <div class="content">
                      <h2 class="name">{{food.name}}</h2>
                      <p class="desc">{{food.description}}</p>
                      <div class="extra">
                        <span class="count">月售{{food.sellCount}}份</span>
                        <span>好评率{{food.rating}}%</span>
                      </div>
                      <div class="price">
                        <span class="now">¥{{food.price}}</span>
                        <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                      </div>
                      <div class="cartcontrol-wrapper">
                        <cartcontrol :food="food"></cartcontrol>
                      </div>
                    </div>
                  </li>
                </ul>
              </li>
            </ul>
          </div>
          <shopcart ref:shopcart :select-foods="selectFoods" :delivery-price="seller.deliveryPrice" :min-price="seller.minPrice"></shopcart>
        </div>
        <food :food="selectedFood" ref="food"></food>
      </div>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import shopcart from '../../components/shopcart/shopcart';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      import food from '../../components/food/food';
      const ERR_OK = 0;
      export default {
        name: 'v-goods',
        props: {
          seller: {
            type: Object
          }
        },
        data() {
          return {
            goods: [],
            listHeight: [],
            scrollY: 0,
            selectedFood: {}
          };
        },
        computed: {
          currentIndex() {
            for (let i = 0; i < this.listHeight.length; i++) {
              let height1 = this.listHeight[i];
              let height2 = this.listHeight[i + 1];
              if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)) {
                return i;
              }
            }
            return 0;
          },
          selectFoods() {
            let foods = [];
            this.goods.forEach((good) => {
              good.foods.forEach((food) => {
                if (food.count) {
                  foods.push(food);
                }
              });
            });
            return foods;
          }
        },
        created() {
          this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
          this.$http.get('api/goods').then((response) => {
            response = response.body;
            if (response.error === ERR_OK) {
              this.goods = response.data;
            }
            this.goods = response.data;
            this.$nextTick(() => {
              this._initScroll();
              this._calculateHeight();
            });
          });
        },
        methods: {
          selectMenu(index, event) {
            if (!event._constructed) {
              return;
            }
            let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
            let el = foodList[index];
            this.foodsScroll.scrollToElement(el, 300);
            console.log(index);
          },
          selectFood(food, event) {
            if (!event._constructed) {
              return;
            }
            this.selectedFood = food;
            this.$refs.food.show();
          },
          _drop(target) {
            //体验优化,异步执行下落动画
            this.$nextTick(() => {
              this.$refs.shopcart.drop(target);
            });
          },
          _initScroll() {
            this.menuScroll = new BScroll(this.$refs.menuWrapper, {
              click: true
            });
    
            this.foodsScroll = new BScroll(this.$refs.foodsWrapper, {
              click: true,
              probeType: 3
            });
    
            this.foodsScroll.on("scroll", (pos) => {
              this.scrollY = Math.abs(Math.round(pos.y));
            });
          },
          _calculateHeight() {
            let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
            let height = 0;
            this.listHeight.push(height);
            for (let i = 0; i < foodList.length; i++) {
              let item = foodList[i];
              height += item.clientHeight;
              this.listHeight.push(height);
            }
          }
        },
        components: {
          shopcart,
          cartcontrol,
          food
        },
        events: {
          'cart.add' (target) {
            this._drop(target);
          }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
        @import "../../common/stylus/mixin";
        .goods
            display: flex
            position: absolute
            top: 174px
            bottom: 46px
             100%
            overflow: hidden
            .menu-wrapper
                flex: 0 0 80px
                 80px
                background: #f3f5f7
                .menu-item
                    display: table
                    height: 54px
                     56px
                    padding: 0 12px
                    line-height: 14px
                    &.current
                        position: relative
                        z-index: 10
                        margin-top: -1px
                        background: #ccc
                        font-weight: 700
                        .text
                            border-none()
                    .icon
                        display: inline-block
                        vertical-align: top
                         12px
                        height: 12px
                        margin-right: 2px
                        background-size: 12px 12px
                        background-repeat: no-repeat
                        &.decrease
                                bg-image('decrease_3')
                            &.discount
                                bg-image('discount_3')
                            &.guarantee
                                bg-image('guarantee_3')
                            &.invoice
                                bg-image('invoice_3')
                            &.special
                                bg-image('special_3')
                    .text
                        display: table-cell
                         56px
                        vertical-align: middle
                        border-1px(rgba(7,17,27,0.1))
                        font-size: 12px
            .foods-wrapper
                flex: 1
                .title
                    padding-left: 14px
                    height: 26px
                    line-height: 26px
                    border-left: 2px solid #d9dde1
                    font-size: 12px
                    color: rgb(147,153,159)
                    background: #f3f5f7
                .food-item
                    display: flex
                    margin: 18px
                    padding-bottom: 18px
                    border-1px(rgba(7,17,27,0.1))
                    &:last-child
                        border-none()
                        margin-bottom: 0
                    .icon
                        flex: 0 0 57px
                        margin-right: 10px
                    .content
                        flex: 1
                        .name
                            margin: 2px 0 8px 0
                            height: 14px
                            line-height: 14px
                            font-size: 14px
                            color: rgb(7,17,27)
                        .desc, .extra                    
                            line-height: 10px
                            font-size: 10px
                            color: rgb(147,153,159)
                        .desc
                            margin-bottom: 8px
                            line-height: 14px
                        .extra
                            .count
                                margin-right: 12px
                        .price
                            font-weight: 700
                            line-height: 24px
                            .now
                                margin-right: 8px
                                font-size: 14px
                                color: rgb(240,20,20)
                            .old
                                text-decoration: line-through
                                font-size: 10px
                                color: rgb(147,153,159)
                        .cartcontrol-wrapper
                            position: absolute
                            right: 0
                            bottom: 12px
    </style>
    View Code

    步骤二:编写food.vue组件,实现切换动画效果

    <template>
      <transition name="move">
        <div v-show="showFlag" class="food">
          <div class="food-content">
            <div class="image-header">
              <img :src="food.image" />
            </div>
          </div>
        </div>
      </transition>
    </template>
    
    <script>
      export default {
        name: 'v-food',
        props: {
          food: {
            type: Object
          }
        },
        data() {
          return {
            showFlag: false
          };
        },
        methods: {
          show() {
            this.showFlag = true;
          }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30px
         100%
        background: #fff
        transform: translate3d(0, 0, 0)
        &.move-enter-active, &.move-leave-active
          transition: all 0.2s linear
        &.move-enter, &.move-leave-active
          transform: translate3d(100%, 0, 0)
    </style>
    View Code

    商品详情实现(三)

    编写food.vue组件,实现后退效果以及部分内容展示

    <template>
      <transition name="move">
        <div v-show="showFlag" class="food">
          <div class="food-content">
            <div class="image-header">
              <img :src="food.image" />
              <div class="back" @click="hide">
                <i class="icon-arrow_lift"></i>
              </div>
            </div>
            <div class="content">
              <h1 class="title">{{food.name}}</h1>
              <div class="detail">
                <span class="sell-count">月售{{food.sellCount}}份</span>
                <span class="rating">好评率{{food.rating}}</span>
              </div>
              <div class="price">
                <span class="now">¥{{food.price}}</span>
                <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
              </div>
            </div>
          </div>
        </div>
      </transition>
    </template>
    <script>
      export default {
        name: 'v-food',
        props: {
          food: {
            type: Object
          }
        },
        data() {
          return {
            showFlag: false
          };
        },
        methods: {
          show() {
            this.showFlag = true;
          },
          hide() {
            this.showFlag = false;
          }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30px
         100%
        background: #fff
        transform: translate3d(0, 0, 0)
        &.move-enter-active, &.move-leave-active
          transition: all 0.2s linear
        &.move-enter, &.move-leave-active
          transform: translate3d(100%, 0, 0)
        .image-header
          position: relative
           100%
          height: 0
          padding-top: 100%
          img
            position: absolute
            top: 0
            left: 0
             100%
            height: 100%
          .back
            position: absolute
            top: 10px
            left: 0
            .icon-arrow_lift
              display: block
              padding: 10px
              font-size: 20px
              color: #fff
    </style>
    View Code

    商品详情实现(四)

    编写food.vue组件,增加cartcontrol.vue组件

    <template>
      <transition name="move">
        <div v-show="showFlag" class="food" ref="food">
          <div class="food-content">
            <div class="image-header">
              <img :src="food.image" />
              <div class="back" @click="hide">
                <i class="icon-arrow_lift"></i>
              </div>
            </div>
            <div class="content">
              <h1 class="title">{{food.name}}</h1>
              <div class="detail">
                <span class="sell-count">月售{{food.sellCount}}份</span>
                <span class="rating">好评率{{food.rating}}%</span>
              </div>
              <div class="price">
                <span class="now">¥{{food.price}}</span>
                <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
              </div>
            </div>
            <div class="cartcontrol-wrapper">
              <cartcontrol :food="food"></cartcontrol>
            </div>
          </div>
        </div>
      </transition>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      export default {
        name: 'v-food',
        props: {
          food: {
            type: Object
          }
        },
        data() {
          return {
            showFlag: false
          };
        },
        methods: {
          show() {
            this.showFlag = true;
            this.$nextTick(() => {
              if (!this.scroll) {
                this.scroll = new BScroll(this.$refs.food, {
                  click: true
                });
              } else {
                this.scroll.refresh();
              }
            });
          },
          hide() {
            this.showFlag = false;
          }
        },
        components: {
          cartcontrol
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30px
         100%
        background: #fff
        transform: translate3d(0, 0, 0)
        &.move-enter-active, &.move-leave-active
          transition: all 0.2s linear
        &.move-enter, &.move-leave-active
          transform: translate3d(100%, 0, 0)
        .image-header
          position: relative
           100%
          height: 0
          padding-top: 100%
          img
            position: absolute
            top: 0
            left: 0
             100%
            height: 100%
          .back
            position: absolute
            top: 10px
            left: 0
            .icon-arrow_lift
              display: block
              padding: 10px
              font-size: 20px
              color: #fff
        .content
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 8px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .detail
            margin-bottom: 18px
            line-height: 10px
            height: 10px
            font-size: 0
            .sell-count, .rating
              font-size: 10px
              color: rgb(147, 153, 159)
            .sell-count
              margin-right: 12px
          .price
            font-weight: 700
            line-height: 24px
            .now
              margin-right: 8px
              font-size: 14px
              color: rgb(240, 20, 20)
            .old
              text-decoration: line-through
              font-size: 10px
              color: rgb(147, 153, 159)
    </style>
    View Code

    商品详情实现(五)

    步骤一:编写food.vue组件,实现添加购物车按钮(动画效果依然无法实现)

    <template>
      <transition name="move">
        <div v-show="showFlag" class="food" ref="food">
          <div class="food-content">
            <div class="image-header">
              <img :src="food.image" />
              <div class="back" @click="hide">
                <i class="icon-arrow_lift"></i>
              </div>
            </div>
            <div class="content">
              <h1 class="title">{{food.name}}</h1>
              <div class="detail">
                <span class="sell-count">月售{{food.sellCount}}份</span>
                <span class="rating">好评率{{food.rating}}%</span>
              </div>
              <div class="price">
                <span class="now">¥{{food.price}}</span>
                <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
              </div>
            </div>
            <div class="cartcontrol-wrapper">
              <cartcontrol :food="food"></cartcontrol>
            </div>
            <transition name="fade">
              <div @click.stop.prevent="addFirst" class="buy" v-show="!food.count || food.count===0">
                加入购物车
              </div>
            </transition>
          </div>
        </div>
      </transition>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import Vue from 'vue';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      export default {
        name: 'v-food',
        props: {
          food: {
            type: Object
          }
        },
        data() {
          return {
            showFlag: false
          };
        },
        methods: {
          show() {
            this.showFlag = true;
            this.$nextTick(() => {
              if (!this.scroll) {
                this.scroll = new BScroll(this.$refs.food, {
                  click: true
                });
              } else {
                this.scroll.refresh();
              }
            });
          },
          hide() {
            this.showFlag = false;
          },
          addFirst(event) {
              if(!event._constructed) {
                  return;
              }
              console.log(event.target);
              this.$emit('add', event.target);
            Vue.set(this.food, 'count', 1);
          }
        },
        components: {
          cartcontrol
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30px
         100%
        background: #fff
        transform: translate3d(0, 0, 0)
        &.move-enter-active, &.move-leave-active
          transition: all 0.2s linear
        &.move-enter, &.move-leave-active
          transform: translate3d(100%, 0, 0)
        .image-header
          position: relative
           100%
          height: 0
          padding-top: 100%
          img
            position: absolute
            top: 0
            left: 0
             100%
            height: 100%
          .back
            position: absolute
            top: 10px
            left: 0
            .icon-arrow_lift
              display: block
              padding: 10px
              font-size: 20px
              color: #fff
        .content
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 8px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .detail
            margin-bottom: 18px
            line-height: 10px
            height: 10px
            font-size: 0
            .sell-count, .rating
              font-size: 10px
              color: rgb(147, 153, 159)
            .sell-count
              margin-right: 12px
          .price
            font-weight: 700
            line-height: 24px
            .now
              margin-right: 8px
              font-size: 14px
              color: rgb(240, 20, 20)
            .old
              text-decoration: line-through
              font-size: 10px
              color: rgb(147, 153, 159)
        .cartcontrol-wrapper
          position: absolute
          right: 12px
          bottom: 12px
        .buy
          position: absolute
          right: 18px
          bottom: 18px
          z-index: 10
          height: 24px
          line-height: 24px
          padding: 0 12px
          box-sizing: border-box
          border-radius: 12px
          font-size: 10px
          color: #fff
          background: rgb(0, 160, 220)
          opacity: 1
          &.fade-enter-active, &.fade-leave-active
            transition: all 0.2s
          &.fade-enter, &.fade-leave-active
            opacity: 0
            z-index: -1
    </style>
    View Code

    步骤二:编写cartcontrol.vue组件,阻止点击事件冒泡行为

    <template>
      <div class="cartcontrol">
        <transition name="move">
          <div class="cart-decrease" v-show="food.count>0" @click.stop.prevent="decreaseCart">
            <span class="inner icon-remove_circle_outline"></span>
          </div>
        </transition>
        <div class="cart-count" v-show="food.count>0">{{food.count}}</div>
        <div class="cart-add icon-add_circle" @click.stop.prevent="addCart"></div>
      </div>
    </template>
    
    <script>
      import Vue from 'vue';
      export default {
        name: 'v-cartcontrol',
        props: {
          food: {
            type: Object
          }
        },
        methods: {
          addCart(event) {
            if (!event._constructed) {
              return;
            }
            if (!this.food.count) {
              Vue.set(this.food, 'count', 1);
            } else {
              this.food.count++;
            }
          },
          decreaseCart(event) {
            if (!event._constructed) {
              return;
            }
            if (this.food.count) {
              this.food.count--;
            }
          }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .cartcontrol
        font-size: 0
        .cart-decrease
          padding: 6px
          color: rgb(0,160,220)
          display: inline-block
          opacity: 1
          transform: translate3d(0, 0, 0)
          .inner
            display: inline-block
            line-height: 24px
            font-size: 24px
            color: rgb(0, 160, 220)
            transition: all 0.4s linear
            transform: rotate(0)
          &.move-enter-active, &.move-leave-active
            transition: all 0.4s linear
          &.move-enter, &.move-leave-active
            opacity: 0
            transform: translate3d(24px, 0, 0)
            .inner
              transform: rotate(180deg)
        .cart-count
          display: inline-block
          vertical-align: top
           12px
          padding-top: 6px
          line-height: 24px
          text-align: center
          font-size: 10px
          color: rgb(147,153,159)
        .cart-add
          display: inline-block
          padding: 6px
          line-height: 24px
          font-size: 24px
          color: rgb(0, 160, 220)
    </style>
    View Code

    split组件实现

    步骤一:新建split.vue组件

    <template>
      <div class="split"></div>
    </template>
    
    <script>
      export default {};
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      .split
         100%
        height: 16px
        border-top: 1px solid rgba(7, 17, 27, 0.1)
        border-bottom: 1px solid rgba(7, 17, 27, 0.1)
        /*background: #f3f5f7*/
        background: #ccc
    </style>
    View Code

    步骤二:编写food.vue组件,继续添加内容和添加split.vue组件

    <template>
      <transition name="move">
        <div v-show="showFlag" class="food" ref="food">
          <div class="food-content">
            <div class="image-header">
              <img :src="food.image" />
              <div class="back" @click="hide">
                <i class="icon-arrow_lift"></i>
              </div>
            </div>
            <div class="content">
              <h1 class="title">{{food.name}}</h1>
              <div class="detail">
                <span class="sell-count">月售{{food.sellCount}}份</span>
                <span class="rating">好评率{{food.rating}}%</span>
              </div>
              <div class="price">
                <span class="now">¥{{food.price}}</span>
                <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
              </div>
              <div class="cartcontrol-wrapper">
                <cartcontrol :food="food"></cartcontrol>
              </div>
              <transition name="fade">
                <div @click.stop.prevent="addFirst" class="buy" v-show="!food.count || food.count===0">
                  加入购物车
                </div>
              </transition>
            </div>
            <split></split>
            <div class="info" v-show="food.info">
                <h1 class="title">商品信息</h1>
                <p class="text">{{food.info}}</p>
            </div>
          </div>
        </div>
      </transition>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import Vue from 'vue';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      import split from '../../components/split/split';
      export default {
        name: 'v-food',
        props: {
          food: {
            type: Object
          }
        },
        data() {
          return {
            showFlag: false
          };
        },
        methods: {
          show() {
            this.showFlag = true;
            this.$nextTick(() => {
              if (!this.scroll) {
                this.scroll = new BScroll(this.$refs.food, {
                  click: true
                });
              } else {
                this.scroll.refresh();
              }
            });
          },
          hide() {
            this.showFlag = false;
          },
          addFirst(event) {
              if(!event._constructed) {
                  return;
              }
              this.$emit('add', event.target);
            Vue.set(this.food, 'count', 1);
          }
        },
        components: {
          cartcontrol,
          split
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30px
         100%
        background: #fff
        transform: translate3d(0, 0, 0)
        &.move-enter-active, &.move-leave-active
          transition: all 0.2s linear
        &.move-enter, &.move-leave-active
          transform: translate3d(100%, 0, 0)
        .image-header
          position: relative
           100%
          height: 0
          padding-top: 100%
          img
            position: absolute
            top: 0
            left: 0
             100%
            height: 100%
          .back
            position: absolute
            top: 10px
            left: 0
            .icon-arrow_lift
              display: block
              padding: 10px
              font-size: 20px
              color: #fff
        .content
          position: relative
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 8px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .detail
            margin-bottom: 18px
            line-height: 10px
            height: 10px
            font-size: 0
            .sell-count, .rating
              font-size: 10px
              color: rgb(147, 153, 159)
            .sell-count
              margin-right: 12px
          .price
            font-weight: 700
            line-height: 24px
            .now
              margin-right: 8px
              font-size: 14px
              color: rgb(240, 20, 20)
            .old
              text-decoration: line-through
              font-size: 10px
              color: rgb(147, 153, 159)
          .cartcontrol-wrapper
            position: absolute
            right: 12px
            bottom: 12px
          .buy
            position: absolute
            right: 18px
            bottom: 18px
            z-index: 10
            height: 24px
            line-height: 24px
            padding: 0 12px
            box-sizing: border-box
            border-radius: 12px
            font-size: 10px
            color: #fff
            background: rgb(0, 160, 220)
            opacity: 1
            &.fade-enter-active, &.fade-leave-active
              transition: all 0.2s
            &.fade-enter, &.fade-leave-active
              opacity: 0
              z-index: -1
        .info
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 6px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .text
            line-height: 24px
            padding: 0 8px
            font-size: 12px
            color: rgb(77, 85, 93)
    </style>
    View Code

    ratingselect组件(一)

    步骤一:新建ratingselect.vue组件

    <template>
      <div class="ratingselect">
        <div class="rating-type">
          <span>{{desc.all}}</span>
          <span>{{desc.positive}}</span>
          <span>{{desc.negative}}</span>
        </div>
        <div class="switch">
          <span class="icon-check_circle"></span>
          <span class="text">只看有内容的评价</span>
        </div>
      </div>
    </template>
    
    <script>
      const POSITIVE = 0;
      const NEGATIVE = 1;
      const ALL = 2;
    
      export default {
        props: {
          ratings: {
            type: Array,
            default () {
              return [];
            }
          },
          selectType: {
            type: Number,
            default: ALL
          },
          onlyContent: {
            type: Boolean,
            default: false
          },
          desc: {
            type: Object,
            default () {
              return {
                all: '全部',
                positive: '满意',
                negative: '不满意'
              };
            }
          }
        }
      };
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
    
    </style>
    View Code

    步骤二:编写food.vue组件,将ratingselect.vue组件加载

    <template>
      <transition name="move">
        <div v-show="showFlag" class="food" ref="food">
          <div class="food-content">
            <div class="image-header">
              <img :src="food.image" />
              <div class="back" @click="hide">
                <i class="icon-arrow_lift"></i>
              </div>
            </div>
            <div class="content">
              <h1 class="title">{{food.name}}</h1>
              <div class="detail">
                <span class="sell-count">月售{{food.sellCount}}份</span>
                <span class="rating">好评率{{food.rating}}%</span>
              </div>
              <div class="price">
                <span class="now">¥{{food.price}}</span>
                <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
              </div>
              <div class="cartcontrol-wrapper">
                <cartcontrol :food="food"></cartcontrol>
              </div>
              <transition name="fade">
                <div @click.stop.prevent="addFirst" class="buy" v-show="!food.count || food.count===0">
                  加入购物车
                </div>
              </transition>
            </div>
            <split v-show="food.info"></split>
            <div class="info" v-show="food.info">
                <h1 class="title">商品信息</h1>
                <p class="text">{{food.info}}</p>
            </div>
            <split></split>
            <div class="rating">
                <h1 class="title">商品评价</h1>
                <ratingselect></ratingselect>
            </div>
          </div>
        </div>
      </transition>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import Vue from 'vue';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      import split from '../../components/split/split';
      import ratingselect from '../../components/ratingselect/ratingselect';
      export default {
        name: 'v-food',
        props: {
          food: {
            type: Object
          }
        },
        data() {
          return {
            showFlag: false
          };
        },
        methods: {
          show() {
            this.showFlag = true;
            this.$nextTick(() => {
              if (!this.scroll) {
                this.scroll = new BScroll(this.$refs.food, {
                  click: true
                });
              } else {
                this.scroll.refresh();
              }
            });
          },
          hide() {
            this.showFlag = false;
          },
          addFirst(event) {
              if(!event._constructed) {
                  return;
              }
              this.$emit('add', event.target);
            Vue.set(this.food, 'count', 1);
          }
        },
        components: {
          cartcontrol,
          split,
          ratingselect
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30px
         100%
        background: #fff
        transform: translate3d(0, 0, 0)
        &.move-enter-active, &.move-leave-active
          transition: all 0.2s linear
        &.move-enter, &.move-leave-active
          transform: translate3d(100%, 0, 0)
        .image-header
          position: relative
           100%
          height: 0
          padding-top: 100%
          img
            position: absolute
            top: 0
            left: 0
             100%
            height: 100%
          .back
            position: absolute
            top: 10px
            left: 0
            .icon-arrow_lift
              display: block
              padding: 10px
              font-size: 20px
              color: #fff
        .content
          position: relative
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 8px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .detail
            margin-bottom: 18px
            line-height: 10px
            height: 10px
            font-size: 0
            .sell-count, .rating
              font-size: 10px
              color: rgb(147, 153, 159)
            .sell-count
              margin-right: 12px
          .price
            font-weight: 700
            line-height: 24px
            .now
              margin-right: 8px
              font-size: 14px
              color: rgb(240, 20, 20)
            .old
              text-decoration: line-through
              font-size: 10px
              color: rgb(147, 153, 159)
          .cartcontrol-wrapper
            position: absolute
            right: 12px
            bottom: 12px
          .buy
            position: absolute
            right: 18px
            bottom: 18px
            z-index: 10
            height: 24px
            line-height: 24px
            padding: 0 12px
            box-sizing: border-box
            border-radius: 12px
            font-size: 10px
            color: #fff
            background: rgb(0, 160, 220)
            opacity: 1
            &.fade-enter-active, &.fade-leave-active
              transition: all 0.2s
            &.fade-enter, &.fade-leave-active
              opacity: 0
              z-index: -1
        .info
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 6px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .text
            line-height: 24px
            padding: 0 8px
            font-size: 12px
            color: rgb(77, 85, 93)
    </style>
    View Code

    ratingselect组件(二)

    编写food.vue组件,实现部分ratingselect.vue效果

    <template>
      <transition name="move">
        <div v-show="showFlag" class="food" ref="food">
          <div class="food-content">
            <div class="image-header">
              <img :src="food.image" />
              <div class="back" @click="hide">
                <i class="icon-arrow_lift"></i>
              </div>
            </div>
            <div class="content">
              <h1 class="title">{{food.name}}</h1>
              <div class="detail">
                <span class="sell-count">月售{{food.sellCount}}份</span>
                <span class="rating">好评率{{food.rating}}%</span>
              </div>
              <div class="price">
                <span class="now">¥{{food.price}}</span>
                <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
              </div>
              <div class="cartcontrol-wrapper">
                <cartcontrol :food="food"></cartcontrol>
              </div>
              <transition name="fade">
                <div @click.stop.prevent="addFirst" class="buy" v-show="!food.count || food.count===0">
                  加入购物车
                </div>
              </transition>
            </div>
            <split v-show="food.info"></split>
            <div class="info" v-show="food.info">
              <h1 class="title">商品信息</h1>
              <p class="text">{{food.info}}</p>
            </div>
            <split></split>
            <div class="rating">
              <h1 class="title">商品评价</h1>
              <ratingselect :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect>
            </div>
          </div>
        </div>
      </transition>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import Vue from 'vue';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      import split from '../../components/split/split';
      import ratingselect from '../../components/ratingselect/ratingselect';
    
      const POSITIVE = 0;
      const NEGATIVE = 1;
      const ALL = 2;
      export default {
        name: 'v-food',
        props: {
          food: {
            type: Object
          }
        },
        data() {
          return {
            showFlag: false,
            selectType: ALL,
            onlyContent: true,
            desc: {
              all: '全部',
              positive: '推荐',
              negative: '吐槽'
            }
          };
        },
        methods: {
          show() {
            this.showFlag = true;
            this.selectType = ALL;
            this.onlyContent = true;
            this.$nextTick(() => {
              if (!this.scroll) {
                this.scroll = new BScroll(this.$refs.food, {
                  click: true
                });
              } else {
                this.scroll.refresh();
              }
            });
          },
          hide() {
            this.showFlag = false;
          },
          addFirst(event) {
            if (!event._constructed) {
              return;
            }
            this.$emit('add', event.target);
            Vue.set(this.food, 'count', 1);
          }
        },
        components: {
          cartcontrol,
          split,
          ratingselect
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30px
         100%
        background: #fff
        transform: translate3d(0, 0, 0)
        &.move-enter-active, &.move-leave-active
          transition: all 0.2s linear
        &.move-enter, &.move-leave-active
          transform: translate3d(100%, 0, 0)
        .image-header
          position: relative
           100%
          height: 0
          padding-top: 100%
          img
            position: absolute
            top: 0
            left: 0
             100%
            height: 100%
          .back
            position: absolute
            top: 10px
            left: 0
            .icon-arrow_lift
              display: block
              padding: 10px
              font-size: 20px
              color: #fff
        .content
          position: relative
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 8px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .detail
            margin-bottom: 18px
            line-height: 10px
            height: 10px
            font-size: 0
            .sell-count, .rating
              font-size: 10px
              color: rgb(147, 153, 159)
            .sell-count
              margin-right: 12px
          .price
            font-weight: 700
            line-height: 24px
            .now
              margin-right: 8px
              font-size: 14px
              color: rgb(240, 20, 20)
            .old
              text-decoration: line-through
              font-size: 10px
              color: rgb(147, 153, 159)
          .cartcontrol-wrapper
            position: absolute
            right: 12px
            bottom: 12px
          .buy
            position: absolute
            right: 18px
            bottom: 18px
            z-index: 10
            height: 24px
            line-height: 24px
            padding: 0 12px
            box-sizing: border-box
            border-radius: 12px
            font-size: 10px
            color: #fff
            background: rgb(0, 160, 220)
            opacity: 1
            &.fade-enter-active, &.fade-leave-active
              transition: all 0.2s
            &.fade-enter, &.fade-leave-active
              opacity: 0
              z-index: -1
        .info
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 6px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .text
            line-height: 24px
            padding: 0 8px
            font-size: 12px
            color: rgb(77, 85, 93)
        .rating
          padding-top: 18px
          .title
            line-height: 14px
            margin-left: 18px
            font-size: 14px
            color: rgb(7, 17, 27)
    </style>
    View Code

    ratingselect组件(三)

    继续编写ratingselect.vue组件,实现部分样式和动态切换

    <template>
      <div class="ratingselect">
        <div class="rating-type border-1px">
          <span class="block positive" :class="{'active':selectType===2}">
              {{desc.all}}
              <span class="count">47</span>
          </span>
          <span class="block positive" :class="{'active':selectType===0}">
              {{desc.positive}}
              <span class="count">40</span>
          </span>
          <span class="block negative" :class="{'active':selectType===1}">
              {{desc.negative}}
              <span class="count">10</span>
          </span>
        </div>
        <div class="switch">
          <span class="icon-check_circle"></span>
          <span class="text">只看有内容的评价</span>
        </div>
      </div>
    </template>
    
    <script>
      const POSITIVE = 0;
      const NEGATIVE = 1;
      const ALL = 2;
    
      export default {
        props: {
          ratings: {
            type: Array,
            default () {
              return [];
            }
          },
          selectType: {
            type: Number,
            default: ALL
          },
          onlyContent: {
            type: Boolean,
            default: false
          },
          desc: {
            type: Object,
            default () {
              return {
                all: '全部',
                positive: '满意',
                negative: '不满意'
              };
            }
          }
        }
      };
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      .ratingselect
        .rating-type
          padding: 18px 0
          margin: 0 18px
          border-1px(rgba(7, 17, 27, 0.1))
          font-size: 0
          .block
            display: inline-block
            padding: 8px 12px
            margin-right: 8px
            line-height: 16px
            border-radius: 1px
            font-size: 12px
            color: rgb(77, 85, 93)
            &.active
              color: #fff
            .count
              margin-left: 2px
              font-size: 8px
            &.positive
              background: rgba(0, 160, 220, 0.2)
              &.active
                background: rgb(0, 160, 220)
            &.negative
              background: rgba(77, 85, 93, 0.2)
              &.active
                background: rgb(77, 85, 93)
    </style>
    View Code

    ratingselect组件(四)

    继续编写ratingselect.vue组件,进一步实现样式

    <template>
      <div class="ratingselect">
        <div class="rating-type border-1px">
          <span class="block positive" :class="{'active':selectType===2}">
              {{desc.all}}
              <span class="count">47</span>
          </span>
          <span class="block positive" :class="{'active':selectType===0}">
              {{desc.positive}}
              <span class="count">40</span>
          </span>
          <span class="block negative" :class="{'active':selectType===1}">
              {{desc.negative}}
              <span class="count">10</span>
          </span>
        </div>
        <div class="switch" :class="{'on':onlyContent}">
          <span class="icon-check_circle"></span>
          <span class="text">只看有内容的评价</span>
        </div>
      </div>
    </template>
    
    <script>
      const POSITIVE = 0;
      const NEGATIVE = 1;
      const ALL = 2;
    
      export default {
        props: {
          ratings: {
            type: Array,
            default () {
              return [];
            }
          },
          selectType: {
            type: Number,
            default: ALL
          },
          onlyContent: {
            type: Boolean,
            default: false
          },
          desc: {
            type: Object,
            default () {
              return {
                all: '全部',
                positive: '满意',
                negative: '不满意'
              };
            }
          }
        }
      };
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      .ratingselect
        .rating-type
          padding: 18px 0
          margin: 0 18px
          border-1px(rgba(7, 17, 27, 0.1))
          font-size: 0
          .block
            display: inline-block
            padding: 8px 12px
            margin-right: 8px
            line-height: 16px
            border-radius: 1px
            font-size: 12px
            color: rgb(77, 85, 93)
            &.active
              color: #fff
            .count
              margin-left: 2px
              font-size: 8px
            &.positive
              background: rgba(0, 160, 220, 0.2)
              &.active
                background: rgb(0, 160, 220)
            &.negative
              background: rgba(77, 85, 93, 0.2)
              &.active
                background: rgb(77, 85, 93)
        .switch
          padding: 12px 18px
          line-height: 24px
          border-bottom: 1px solid rgba(7, 17, 27, 0.1)
          color: rgb(147, 153, 159)
          font-size: 0
          &.on
            .icon-check_circle
              color: #00c850
          .icon-check_circle
            display: inline-block
            vertical-align: top
            margin-right: 4px
            font-size: 24px
          .text
            display: inline-block
            vertical-align: top
            font-size: 12px
    </style>
    View Code

    ratingselect组件(五)

    继续编写ratingselect.vue组件,完成全部切换效果(点击效果无效)

    <template>
      <div class="ratingselect">
        <div class="rating-type border-1px">
          <span @click="select(2,$event)" class="block positive" :class="{'active':selectType===2}">{{desc.all}}<span
              class="count">{{ratings.length}}</span></span>
          <span @click="select(0,$event)" class="block positive" :class="{'active':selectType===0}">{{desc.positive}}<span
              class="count">{{positives.length}}</span></span>
          <span @click="select(1,$event)" class="block negative" :class="{'active':selectType===1}">{{desc.negative}}<span
              class="count">{{negatives.length}}</span></span>
        </div>
        <div @click="toggleContent" class="switch" :class="{'on':onlyContent}">
          <span class="icon-check_circle"></span>
          <span class="text">只看有内容的评价</span>
        </div>
      </div>
    </template>
    
    <script>
      const POSITIVE = 0;
      const NEGATIVE = 1;
      const ALL = 2;
    
      export default {
        props: {
          ratings: {
            type: Array,
            default() {
              return [];
            }
          },
          selectType: {
            type: Number,
            default: ALL
          },
          onlyContent: {
            type: Boolean,
            default: false
          },
          desc: {
            type: Object,
            default() {
              return {
                all: '全部',
                positive: '满意',
                negative: '不满意'
              };
            }
          }
        },
        computed: {
          positives() {
            return this.ratings.filter((rating) => {
              return rating.rateType === POSITIVE;
            });
          },
          negatives() {
            return this.ratings.filter((rating) => {
              return rating.rateType === NEGATIVE;
            });
          }
        },
        methods: {
          select(type, event) {
            if (!event._constructed) {
              return;
            }
            this.$emit('select', type);
          },
          toggleContent(event) {
            if (!event._constructed) {
              return;
            }
            this.$emit('toggle');
          }
        }
      };
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"
    
      .ratingselect
        .rating-type
          padding: 18px 0
          margin: 0 18px
          border-1px(rgba(7, 17, 27, 0.1))
          font-size: 0
          .block
            display: inline-block
            padding: 8px 12px
            margin-right: 8px
            line-height: 16px
            border-radius: 1px
            font-size: 12px
            color: rgb(77, 85, 93)
            &.active
              color: #fff
            .count
              margin-left: 2px
              font-size: 8px
            &.positive
              background: rgba(0, 160, 220, 0.2)
              &.active
                background: rgb(0, 160, 220)
            &.negative
              background: rgba(77, 85, 93, 0.2)
              &.active
                background: rgb(77, 85, 93)
        .switch
          padding: 12px 18px
          line-height: 24px
          border-bottom: 1px solid rgba(7, 17, 27, 0.1)
          color: rgb(147, 153, 159)
          font-size: 0
          &.on
            .icon-check_circle
              color: #00c850
          .icon-check_circle
            display: inline-block
            vertical-align: top
            margin-right: 4px
            font-size: 24px
          .text
            display: inline-block
            vertical-align: top
            font-size: 12px
    </style>
    View Code

    评价列表(一)

    编写好food.vue组件的评价结构

    <template>
      <transition name="move">
        <div v-show="showFlag" class="food" ref="food">
          <div class="food-content">
            <div class="image-header">
              <img :src="food.image" />
              <div class="back" @click="hide">
                <i class="icon-arrow_lift"></i>
              </div>
            </div>
            <div class="content">
              <h1 class="title">{{food.name}}</h1>
              <div class="detail">
                <span class="sell-count">月售{{food.sellCount}}份</span>
                <span class="rating">好评率{{food.rating}}%</span>
              </div>
              <div class="price">
                <span class="now">¥{{food.price}}</span>
                <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
              </div>
              <div class="cartcontrol-wrapper">
                <cartcontrol :food="food"></cartcontrol>
              </div>
              <transition name="fade">
                <div @click.stop.prevent="addFirst" class="buy" v-show="!food.count || food.count===0">
                  加入购物车
                </div>
              </transition>
            </div>
            <split v-show="food.info"></split>
            <div class="info" v-show="food.info">
              <h1 class="title">商品信息</h1>
              <p class="text">{{food.info}}</p>
            </div>
            <split></split>
            <div class="rating">
              <h1 class="title">商品评价</h1>
              <ratingselect :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect>
              <div class="rating-wrapper">
                <ul v-show="food.ratings && food.ratings.length">
                  <li v-for="rating in food.ratings" class="rating-item">
                    <div class="user">
                      <span class="name">{{rating.username}}</span>
                      <img class="avatar" width="12" height="12" :src="rating.avatar" />
                    </div>
                    <div class="time">{{rating.rateTime}}</div>
                    <p class="text">
                      <span :class="{'icon-thumb_up':rating.rateType===0,'icon-thumb_down':rating.rateType===1}"></span> {{rating.text}}
                    </p>
                  </li>
                </ul>
                <div class="no-rating" v-show="!food.ratings || !food.ratings.length"></div>
              </div>
            </div>
          </div>
        </div>
      </transition>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import Vue from 'vue';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      import split from '../../components/split/split';
      import ratingselect from '../../components/ratingselect/ratingselect';
    
      const POSITIVE = 0;
      const NEGATIVE = 1;
      const ALL = 2;
      export default {
        name: 'v-food',
        props: {
          food: {
            type: Object
          }
        },
        data() {
          return {
            showFlag: false,
            selectType: ALL,
            onlyContent: true,
            desc: {
              all: '全部',
              positive: '推荐',
              negative: '吐槽'
            }
          };
        },
        methods: {
          show() {
            this.showFlag = true;
            this.selectType = ALL;
            this.onlyContent = true;
            this.$nextTick(() => {
              if (!this.scroll) {
                this.scroll = new BScroll(this.$refs.food, {
                  click: true
                });
              } else {
                this.scroll.refresh();
              }
            });
          },
          hide() {
            this.showFlag = false;
          },
          addFirst(event) {
            if (!event._constructed) {
              return;
            }
            this.$emit('add', event.target);
            Vue.set(this.food, 'count', 1);
          }
        },
        components: {
          cartcontrol,
          split,
          ratingselect
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30px
         100%
        background: #fff
        transform: translate3d(0, 0, 0)
        &.move-enter-active, &.move-leave-active
          transition: all 0.2s linear
        &.move-enter, &.move-leave-active
          transform: translate3d(100%, 0, 0)
        .image-header
          position: relative
           100%
          height: 0
          padding-top: 100%
          img
            position: absolute
            top: 0
            left: 0
             100%
            height: 100%
          .back
            position: absolute
            top: 10px
            left: 0
            .icon-arrow_lift
              display: block
              padding: 10px
              font-size: 20px
              color: #fff
        .content
          position: relative
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 8px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .detail
            margin-bottom: 18px
            line-height: 10px
            height: 10px
            font-size: 0
            .sell-count, .rating
              font-size: 10px
              color: rgb(147, 153, 159)
            .sell-count
              margin-right: 12px
          .price
            font-weight: 700
            line-height: 24px
            .now
              margin-right: 8px
              font-size: 14px
              color: rgb(240, 20, 20)
            .old
              text-decoration: line-through
              font-size: 10px
              color: rgb(147, 153, 159)
          .cartcontrol-wrapper
            position: absolute
            right: 12px
            bottom: 12px
          .buy
            position: absolute
            right: 18px
            bottom: 18px
            z-index: 10
            height: 24px
            line-height: 24px
            padding: 0 12px
            box-sizing: border-box
            border-radius: 12px
            font-size: 10px
            color: #fff
            background: rgb(0, 160, 220)
            opacity: 1
            &.fade-enter-active, &.fade-leave-active
              transition: all 0.2s
            &.fade-enter, &.fade-leave-active
              opacity: 0
              z-index: -1
        .info
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 6px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .text
            line-height: 24px
            padding: 0 8px
            font-size: 12px
            color: rgb(77, 85, 93)
        .rating
          padding-top: 18px
          .title
            line-height: 14px
            margin-left: 18px
            font-size: 14px
            color: rgb(7, 17, 27)
    </style>
    View Code

    评论列表(二)

    继续编写food.vue组件的评论区样式

    <template>
      <transition name="move">
        <div v-show="showFlag" class="food" ref="food">
          <div class="food-content">
            <div class="image-header">
              <img :src="food.image" />
              <div class="back" @click="hide">
                <i class="icon-arrow_lift"></i>
              </div>
            </div>
            <div class="content">
              <h1 class="title">{{food.name}}</h1>
              <div class="detail">
                <span class="sell-count">月售{{food.sellCount}}份</span>
                <span class="rating">好评率{{food.rating}}%</span>
              </div>
              <div class="price">
                <span class="now">¥{{food.price}}</span>
                <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
              </div>
              <div class="cartcontrol-wrapper">
                <cartcontrol :food="food"></cartcontrol>
              </div>
              <transition name="fade">
                <div @click.stop.prevent="addFirst" class="buy" v-show="!food.count || food.count===0">
                  加入购物车
                </div>
              </transition>
            </div>
            <split v-show="food.info"></split>
            <div class="info" v-show="food.info">
              <h1 class="title">商品信息</h1>
              <p class="text">{{food.info}}</p>
            </div>
            <split></split>
            <div class="rating">
              <h1 class="title">商品评价</h1>
              <ratingselect :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect>
              <div class="rating-wrapper">
                <ul v-show="food.ratings && food.ratings.length">
                  <li v-for="rating in food.ratings" class="rating-item border-1px">
                    <div class="user">
                      <span class="name">{{rating.username}}</span>
                      <img class="avatar" width="12" height="12" :src="rating.avatar" />
                    </div>
                    <div class="time">{{rating.rateTime}}</div>
                    <p class="text">
                      <span :class="{'icon-thumb_up':rating.rateType===0,'icon-thumb_down':rating.rateType===1}"></span> {{rating.text}}
                    </p>
                  </li>
                </ul>
                <div class="no-rating" v-show="!food.ratings || !food.ratings.length"></div>
              </div>
            </div>
          </div>
        </div>
      </transition>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import Vue from 'vue';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      import split from '../../components/split/split';
      import ratingselect from '../../components/ratingselect/ratingselect';
    
      const POSITIVE = 0;
      const NEGATIVE = 1;
      const ALL = 2;
      export default {
        name: 'v-food',
        props: {
          food: {
            type: Object
          }
        },
        data() {
          return {
            showFlag: false,
            selectType: ALL,
            onlyContent: true,
            desc: {
              all: '全部',
              positive: '推荐',
              negative: '吐槽'
            }
          };
        },
        methods: {
          show() {
            this.showFlag = true;
            this.selectType = ALL;
            this.onlyContent = true;
            this.$nextTick(() => {
              if (!this.scroll) {
                this.scroll = new BScroll(this.$refs.food, {
                  click: true
                });
              } else {
                this.scroll.refresh();
              }
            });
          },
          hide() {
            this.showFlag = false;
          },
          addFirst(event) {
            if (!event._constructed) {
              return;
            }
            this.$emit('add', event.target);
            Vue.set(this.food, 'count', 1);
          }
        },
        components: {
          cartcontrol,
          split,
          ratingselect
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"
      .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30px
         100%
        background: #fff
        transform: translate3d(0, 0, 0)
        &.move-enter-active, &.move-leave-active
          transition: all 0.2s linear
        &.move-enter, &.move-leave-active
          transform: translate3d(100%, 0, 0)
        .image-header
          position: relative
           100%
          height: 0
          padding-top: 100%
          img
            position: absolute
            top: 0
            left: 0
             100%
            height: 100%
          .back
            position: absolute
            top: 10px
            left: 0
            .icon-arrow_lift
              display: block
              padding: 10px
              font-size: 20px
              color: #fff
        .content
          position: relative
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 8px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .detail
            margin-bottom: 18px
            line-height: 10px
            height: 10px
            font-size: 0
            .sell-count, .rating
              font-size: 10px
              color: rgb(147, 153, 159)
            .sell-count
              margin-right: 12px
          .price
            font-weight: 700
            line-height: 24px
            .now
              margin-right: 8px
              font-size: 14px
              color: rgb(240, 20, 20)
            .old
              text-decoration: line-through
              font-size: 10px
              color: rgb(147, 153, 159)
          .cartcontrol-wrapper
            position: absolute
            right: 12px
            bottom: 12px
          .buy
            position: absolute
            right: 18px
            bottom: 18px
            z-index: 10
            height: 24px
            line-height: 24px
            padding: 0 12px
            box-sizing: border-box
            border-radius: 12px
            font-size: 10px
            color: #fff
            background: rgb(0, 160, 220)
            opacity: 1
            &.fade-enter-active, &.fade-leave-active
              transition: all 0.2s
            &.fade-enter, &.fade-leave-active
              opacity: 0
              z-index: -1
        .info
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 6px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .text
            line-height: 24px
            padding: 0 8px
            font-size: 12px
            color: rgb(77, 85, 93)
        .rating
          padding-top: 18px
          .title
            line-height: 14px
            margin-left: 18px
            font-size: 14px
            color: rgb(7, 17, 27)
          .rating-wrapper
            padding: 0 18px
            .rating-item
              position: relative
              padding: 16px 0
              border-1px(rgba(7, 17, 27, 0.1))
              .user
                position: absolute
                right: 0
                top: 16px
                line-height: 12px
                font-size: 0
                .name
                  display: inline-block
                  margin-right: 6px
                  vertical-align: top
                  font-size: 10px
                  color: rgb(147, 153, 159)
                .avatar
                  border-radius: 50%
              .time
                margin-bottom: 6px
                line-height: 12px
                font-size: 10px
                color: rgb(147, 153, 159)
              .text
                line-height: 16px
                font-size: 12px
                color: rgb(7, 17, 27)
                .icon-thumb_up, .icon-thumb_down
                  margin-right: 4px
                  line-height: 16px
                  font-size: 12px
                .icon-thumb_up
                  color: rgb(0, 160, 220)
                .icon-thumb_down
                  color: rgb(147, 153, 159)
            .no-rating
              padding: 16px 0
              font-size: 12px
              color: rgb(147, 153, 159)
    </style>
    View Code

    评论列表(三)

    继续编写food.vue组件,实现评价切换效果(失败)

    <template>
      <transition name="move">
        <div v-show="showFlag" class="food" ref="food">
          <div class="food-content">
            <div class="image-header">
              <img :src="food.image" />
              <div class="back" @click="hide">
                <i class="icon-arrow_lift"></i>
              </div>
            </div>
            <div class="content">
              <h1 class="title">{{food.name}}</h1>
              <div class="detail">
                <span class="sell-count">月售{{food.sellCount}}份</span>
                <span class="rating">好评率{{food.rating}}%</span>
              </div>
              <div class="price">
                <span class="now">¥{{food.price}}</span>
                <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
              </div>
              <div class="cartcontrol-wrapper">
                <cartcontrol :food="food"></cartcontrol>
              </div>
              <transition name="fade">
                <div @click.stop.prevent="addFirst" class="buy" v-show="!food.count || food.count===0">
                  加入购物车
                </div>
              </transition>
            </div>
            <split v-show="food.info"></split>
            <div class="info" v-show="food.info">
              <h1 class="title">商品信息</h1>
              <p class="text">{{food.info}}</p>
            </div>
            <split></split>
            <div class="rating">
              <h1 class="title">商品评价</h1>
              <ratingselect :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect>
              <div class="rating-wrapper">
                <ul v-show="food.ratings && food.ratings.length">
                  <li v-show="needShow(rating.rateType,rating.text)" v-for="rating in food.ratings" class="rating-item border-1px">
                    <div class="user">
                      <span class="name">{{rating.username}}</span>
                      <img class="avatar" width="12" height="12" :src="rating.avatar" />
                    </div>
                    <div class="time">{{rating.rateTime}}</div>
                    <p class="text">
                      <span :class="{'icon-thumb_up':rating.rateType===0,'icon-thumb_down':rating.rateType===1}"></span> {{rating.text}}
                    </p>
                  </li>
                </ul>
                <div class="no-rating" v-show="!food.ratings || !food.ratings.length"></div>
              </div>
            </div>
          </div>
        </div>
      </transition>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import Vue from 'vue';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      import split from '../../components/split/split';
      import ratingselect from '../../components/ratingselect/ratingselect';
    
      const POSITIVE = 0;
      const NEGATIVE = 1;
      const ALL = 2;
      export default {
        name: 'v-food',
        props: {
          food: {
            type: Object
          }
        },
        data() {
          return {
            showFlag: false,
            selectType: ALL,
            onlyContent: true,
            desc: {
              all: '全部',
              positive: '推荐',
              negative: '吐槽'
            }
          };
        },
        methods: {
          show() {
            this.showFlag = true;
            this.selectType = ALL;
            this.onlyContent = true;
            this.$nextTick(() => {
              if (!this.scroll) {
                this.scroll = new BScroll(this.$refs.food, {
                  click: true
                });
              } else {
                this.scroll.refresh();
              }
            });
          },
          hide() {
            this.showFlag = false;
          },
          addFirst(event) {
            if (!event._constructed) {
              return;
            }
            this.$emit('add', event.target);
            Vue.set(this.food, 'count', 1);
          },
          needShow(type, text) {
            if (this.onlyContent && !text) {
              return false;
            }
            if (this.selectType === ALL) {
              return true;
            } else {
              return type === this.selectType;
            }
          }
        },
        events: {
            'ratingtype.select'(type) {
                this.selectType = type;
                this.$nextTick(() => {
                    this.scroll.refresh();
                });
            },
            'content.toggle'(onlyContent) {
                this.onlyContent = onlyContent;
                this.$nextTick(() => {
                    this.scroll.refresh();
                });
            }
        },
        components: {
          cartcontrol,
          split,
          ratingselect
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"
      .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30px
         100%
        background: #fff
        transform: translate3d(0, 0, 0)
        &.move-enter-active, &.move-leave-active
          transition: all 0.2s linear
        &.move-enter, &.move-leave-active
          transform: translate3d(100%, 0, 0)
        .image-header
          position: relative
           100%
          height: 0
          padding-top: 100%
          img
            position: absolute
            top: 0
            left: 0
             100%
            height: 100%
          .back
            position: absolute
            top: 10px
            left: 0
            .icon-arrow_lift
              display: block
              padding: 10px
              font-size: 20px
              color: #fff
        .content
          position: relative
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 8px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .detail
            margin-bottom: 18px
            line-height: 10px
            height: 10px
            font-size: 0
            .sell-count, .rating
              font-size: 10px
              color: rgb(147, 153, 159)
            .sell-count
              margin-right: 12px
          .price
            font-weight: 700
            line-height: 24px
            .now
              margin-right: 8px
              font-size: 14px
              color: rgb(240, 20, 20)
            .old
              text-decoration: line-through
              font-size: 10px
              color: rgb(147, 153, 159)
          .cartcontrol-wrapper
            position: absolute
            right: 12px
            bottom: 12px
          .buy
            position: absolute
            right: 18px
            bottom: 18px
            z-index: 10
            height: 24px
            line-height: 24px
            padding: 0 12px
            box-sizing: border-box
            border-radius: 12px
            font-size: 10px
            color: #fff
            background: rgb(0, 160, 220)
            opacity: 1
            &.fade-enter-active, &.fade-leave-active
              transition: all 0.2s
            &.fade-enter, &.fade-leave-active
              opacity: 0
              z-index: -1
        .info
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 6px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .text
            line-height: 24px
            padding: 0 8px
            font-size: 12px
            color: rgb(77, 85, 93)
        .rating
          padding-top: 18px
          .title
            line-height: 14px
            margin-left: 18px
            font-size: 14px
            color: rgb(7, 17, 27)
          .rating-wrapper
            padding: 0 18px
            .rating-item
              position: relative
              padding: 16px 0
              border-1px(rgba(7, 17, 27, 0.1))
              .user
                position: absolute
                right: 0
                top: 16px
                line-height: 12px
                font-size: 0
                .name
                  display: inline-block
                  margin-right: 6px
                  vertical-align: top
                  font-size: 10px
                  color: rgb(147, 153, 159)
                .avatar
                  border-radius: 50%
              .time
                margin-bottom: 6px
                line-height: 12px
                font-size: 10px
                color: rgb(147, 153, 159)
              .text
                line-height: 16px
                font-size: 12px
                color: rgb(7, 17, 27)
                .icon-thumb_up, .icon-thumb_down
                  margin-right: 4px
                  line-height: 16px
                  font-size: 12px
                .icon-thumb_up
                  color: rgb(0, 160, 220)
                .icon-thumb_down
                  color: rgb(147, 153, 159)
            .no-rating
              padding: 16px 0
              font-size: 12px
              color: rgb(147, 153, 159)
    </style>
    View Code

    评论列表(四、五、六)

    步骤一:编写food.vue组件,创建一个时间过滤器

    <template>
      <transition name="move">
        <div v-show="showFlag" class="food" ref="food">
          <div class="food-content">
            <div class="image-header">
              <img :src="food.image" />
              <div class="back" @click="hide">
                <i class="icon-arrow_lift"></i>
              </div>
            </div>
            <div class="content">
              <h1 class="title">{{food.name}}</h1>
              <div class="detail">
                <span class="sell-count">月售{{food.sellCount}}份</span>
                <span class="rating">好评率{{food.rating}}%</span>
              </div>
              <div class="price">
                <span class="now">¥{{food.price}}</span>
                <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
              </div>
              <div class="cartcontrol-wrapper">
                <cartcontrol :food="food"></cartcontrol>
              </div>
              <transition name="fade">
                <div @click.stop.prevent="addFirst" class="buy" v-show="!food.count || food.count===0">
                  加入购物车
                </div>
              </transition>
            </div>
            <split v-show="food.info"></split>
            <div class="info" v-show="food.info">
              <h1 class="title">商品信息</h1>
              <p class="text">{{food.info}}</p>
            </div>
            <split></split>
            <div class="rating">
              <h1 class="title">商品评价</h1>
              <ratingselect :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect>
              <div class="rating-wrapper">
                <ul v-show="food.ratings && food.ratings.length">
                  <li v-show="needShow(rating.rateType,rating.text)" v-for="rating in food.ratings" class="rating-item border-1px">
                    <div class="user">
                      <span class="name">{{rating.username}}</span>
                      <img class="avatar" width="12" height="12" :src="rating.avatar" />
                    </div>
                    <div class="time">{{rating.rateTime | formatDate}}</div>
                    <p class="text">
                      <span :class="{'icon-thumb_up':rating.rateType===0,'icon-thumb_down':rating.rateType===1}"></span> 
                      {{rating.text}}
                    </p>
                  </li>
                </ul>
                <div class="no-rating" v-show="!food.ratings || !food.ratings.length">暂无评价</div>
              </div>
            </div>
          </div>
        </div>
      </transition>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import Vue from 'vue';
      import {formatDate} from '../../common/js/date';
      import cartcontrol from '../../components/cartcontrol/cartcontrol';
      import split from '../../components/split/split';
      import ratingselect from '../../components/ratingselect/ratingselect';
    
      const POSITIVE = 0;
      const NEGATIVE = 1;
      const ALL = 2;
      export default {
        name: 'v-food',
        props: {
          food: {
            type: Object
          }
        },
        data() {
          return {
            showFlag: false,
            selectType: ALL,
            onlyContent: true,
            desc: {
              all: '全部',
              positive: '推荐',
              negative: '吐槽'
            }
          };
        },
        methods: {
          show() {
            this.showFlag = true;
            this.selectType = ALL;
            this.onlyContent = true;
            this.$nextTick(() => {
              if (!this.scroll) {
                this.scroll = new BScroll(this.$refs.food, {
                  click: true
                });
              } else {
                this.scroll.refresh();
              }
            });
          },
          hide() {
            this.showFlag = false;
          },
          addFirst(event) {
            if (!event._constructed) {
              return;
            }
            this.$emit('add', event.target);
            Vue.set(this.food, 'count', 1);
          },
          needShow(type, text) {
            if (this.onlyContent && !text) {
              return false;
            }
            if (this.selectType === ALL) {
              return true;
            } else {
              return type === this.selectType;
            }
          }
        },
        events: {
            'ratingtype.select'(type) {
                this.selectType = type;
                this.$nextTick(() => {
                    this.scroll.refresh();
                });
            },
            'content.toggle'(onlyContent) {
                this.onlyContent = onlyContent;
                this.$nextTick(() => {
                    this.scroll.refresh();
                });
            }
        },
        filters: {
          formatDate(time) {
            let date = new Date(time);
            return formatDate(date, 'yyyy-MM-dd hh:mm');
          }
        },
        components: {
          cartcontrol,
          split,
          ratingselect
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"
      .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30px
         100%
        background: #fff
        transform: translate3d(0, 0, 0)
        &.move-enter-active, &.move-leave-active
          transition: all 0.2s linear
        &.move-enter, &.move-leave-active
          transform: translate3d(100%, 0, 0)
        .image-header
          position: relative
           100%
          height: 0
          padding-top: 100%
          img
            position: absolute
            top: 0
            left: 0
             100%
            height: 100%
          .back
            position: absolute
            top: 10px
            left: 0
            .icon-arrow_lift
              display: block
              padding: 10px
              font-size: 20px
              color: #fff
        .content
          position: relative
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 8px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .detail
            margin-bottom: 18px
            line-height: 10px
            height: 10px
            font-size: 0
            .sell-count, .rating
              font-size: 10px
              color: rgb(147, 153, 159)
            .sell-count
              margin-right: 12px
          .price
            font-weight: 700
            line-height: 24px
            .now
              margin-right: 8px
              font-size: 14px
              color: rgb(240, 20, 20)
            .old
              text-decoration: line-through
              font-size: 10px
              color: rgb(147, 153, 159)
          .cartcontrol-wrapper
            position: absolute
            right: 12px
            bottom: 12px
          .buy
            position: absolute
            right: 18px
            bottom: 18px
            z-index: 10
            height: 24px
            line-height: 24px
            padding: 0 12px
            box-sizing: border-box
            border-radius: 12px
            font-size: 10px
            color: #fff
            background: rgb(0, 160, 220)
            opacity: 1
            &.fade-enter-active, &.fade-leave-active
              transition: all 0.2s
            &.fade-enter, &.fade-leave-active
              opacity: 0
              z-index: -1
        .info
          padding: 18px
          .title
            line-height: 14px
            margin-bottom: 6px
            font-size: 14px
            font-weight: 700
            color: rgb(7, 17, 27)
          .text
            line-height: 24px
            padding: 0 8px
            font-size: 12px
            color: rgb(77, 85, 93)
        .rating
          padding-top: 18px
          .title
            line-height: 14px
            margin-left: 18px
            font-size: 14px
            color: rgb(7, 17, 27)
          .rating-wrapper
            padding: 0 18px
            .rating-item
              position: relative
              padding: 16px 0
              border-1px(rgba(7, 17, 27, 0.1))
              .user
                position: absolute
                right: 0
                top: 16px
                line-height: 12px
                font-size: 0
                .name
                  display: inline-block
                  margin-right: 6px
                  vertical-align: top
                  font-size: 10px
                  color: rgb(147, 153, 159)
                .avatar
                  border-radius: 50%
              .time
                margin-bottom: 6px
                line-height: 12px
                font-size: 10px
                color: rgb(147, 153, 159)
              .text
                line-height: 16px
                font-size: 12px
                color: rgb(7, 17, 27)
                .icon-thumb_up, .icon-thumb_down
                  margin-right: 4px
                  line-height: 16px
                  font-size: 12px
                .icon-thumb_up
                  color: rgb(0, 160, 220)
                .icon-thumb_down
                  color: rgb(147, 153, 159)
            .no-rating
              padding: 16px 0
              font-size: 12px
              color: rgb(147, 153, 159)
    </style>
    View Code

    步骤二:编写公共的date.js文件,实现时间过滤的效果

    export function formatDate(date, fmt) {
      if (/(y+)/.test(fmt)) {
        fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
      }
      let o = {
        'M+': date.getMonth() + 1,
        'd+': date.getDate(),
        'h+': date.getHours(),
        'm+': date.getMinutes(),
        's+': date.getSeconds()
      };
      for (let k in o) {
        if (new RegExp(`(${k})`).test(fmt)) {
          let str = o[k] + '';
          fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
        }
      }
      return fmt;
    };
    
    function padLeftZero(str) {
      return ('00' + str).substr(str.length);
    }
    View Code

    rating组件开发(一)

    编写ratings.vue组件,完成左边部分样式

    <template>
      <div class="ratings">
        <div class="ratings-content">
          <div class="overview">
            <div class="overview-left">
              <h1 class="score">{{seller.score}}</h1>
              <div class="title">综合评分</div>
              <div class="rank">高于周边商家{{seller.rankRate}}%</div>
            </div>
            <div class="overview-right"></div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        name: 'v-ratings',
        props: {
          seller: {
            type: Object
          }
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .ratings
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          display: flex
          padding: 18px 0
          .overview-left
            flex: 0 0 137px
            padding: 6px 0
             137px
            border-right: 1px solid rgba(7, 17, 27, 0.1)
            text-align: center
            @media only screen and (max- 320px)
              flex: 0 0 120px
               120px
            .score
              margin-bottom: 6px
              line-height: 28px
              font-size: 24px
              color: rgb(255, 153, 0)
            .title
              margin-bottom: 8px
              line-height: 12px
              font-size: 12px
              color: rgb(7, 17, 27)
            .rank
              line-height: 10px
              font-size: 10px
              color: rgb(147, 153, 159)
          .overview-right
            flex: 1
            padding: 6px 0 6px 24px
    </style>
    View Code

    rating组件开发(二)

    继续编写ratings.vue组件,完成右边部分样式

    <template>
      <div class="ratings">
        <div class="ratings-content">
          <div class="overview">
            <div class="overview-left">
              <h1 class="score">{{seller.score}}</h1>
              <div class="title">综合评分</div>
              <div class="rank">高于周边商家{{seller.rankRate}}%</div>
            </div>
            <div class="overview-right">
                <div class="score-wrapper">
                    <span class="title">服务态度</span>
                    <star :size="36" :score="seller.serviceScore"></star>
                <span class="score">{{seller.serviceScore}}</span>
                </div>
                <div class="score-wrapper">
                    <span class="title">商品评分</span>
                <star :size="36" :score="seller.foodScore"></star>
                <span class="score">{{seller.foodScore}}</span>
                </div>
                <div class="delivery-wrapper">
                <span class="title">送达时间</span>
                <span class="delivery">{{seller.deliveryTime}}分钟</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
        import star from '../../components/star/star';
      export default {
        name: 'v-ratings',
        props: {
          seller: {
            type: Object
          }
        },
        components: {
            star
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .ratings
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          display: flex
          padding: 18px 0
          .overview-left
            flex: 0 0 137px
            padding: 6px 0
             137px
            border-right: 1px solid rgba(7, 17, 27, 0.1)
            text-align: center
            @media only screen and (max- 320px)
              flex: 0 0 120px
               120px
            .score
              margin-bottom: 6px
              line-height: 28px
              font-size: 24px
              color: rgb(255, 153, 0)
            .title
              margin-bottom: 8px
              line-height: 12px
              font-size: 12px
              color: rgb(7, 17, 27)
            .rank
              line-height: 10px
              font-size: 10px
              color: rgb(147, 153, 159)
          .overview-right
            flex: 1
            padding: 6px 0 6px 24px
            @media only screen and (max- 320px)
              padding-left: 6px
            .score-wrapper
              margin-bottom: 8px
              font-size: 0
              .title
                display: inline-block
                line-height: 18px
                vertical-align: top
                font-size: 12px
                color: rgb(7, 17, 27)
              .star
                display: inline-block
                margin: 0 12px
                vertical-align: top
              .score
                display: inline-block
                line-height: 18px
                vertical-align: top
                font-size: 12px
                color: rgb(255, 153, 0)
            .delivery-wrapper
              font-size: 0
              .title
                line-height: 18px
                font-size: 12px
                color: rgb(7, 17, 27)
              .delivery
                margin-left: 12px
                font-size: 12px
                color: rgb(147, 153, 159)
    </style>
    View Code

    rating组件开发(三)

    继续编写ratings.vue组件,利用media实现响应式功能

    <template>
      <div class="ratings">
        <div class="ratings-content">
          <div class="overview">
            <div class="overview-left">
              <h1 class="score">{{seller.score}}</h1>
              <div class="title">综合评分</div>
              <div class="rank">高于周边商家{{seller.rankRate}}%</div>
            </div>
            <div class="overview-right">
                <div class="score-wrapper">
                    <span class="title">服务态度</span>
                    <star :size="36" :score="seller.serviceScore"></star>
                <span class="score">{{seller.serviceScore}}</span>
                </div>
                <div class="score-wrapper">
                    <span class="title">商品评分</span>
                <star :size="36" :score="seller.foodScore"></star>
                <span class="score">{{seller.foodScore}}</span>
                </div>
                <div class="delivery-wrapper">
                <span class="title">送达时间</span>
                <span class="delivery">{{seller.deliveryTime}}分钟</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
        import star from '../../components/star/star';
      export default {
        name: 'v-ratings',
        props: {
          seller: {
            type: Object
          }
        },
        components: {
            star
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .ratings
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          display: flex
          padding: 18px 0
          .overview-left
            flex: 0 0 137px
            padding: 6px 0
             137px
            border-right: 1px solid rgba(7, 17, 27, 0.1)
            text-align: center
            @media only screen and (max- 320px)
              flex: 0 0 120px
               120px
            .score
              margin-bottom: 6px
              line-height: 28px
              font-size: 24px
              color: rgb(255, 153, 0)
            .title
              margin-bottom: 8px
              line-height: 12px
              font-size: 12px
              color: rgb(7, 17, 27)
            .rank
              line-height: 10px
              font-size: 10px
              color: rgb(147, 153, 159)
          .overview-right
            flex: 1
            padding: 6px 0 6px 24px
            @media only screen and (max- 320px)
              padding-left: 6px
            .score-wrapper
              margin-bottom: 8px
              font-size: 0
              .title
                display: inline-block
                line-height: 18px
                vertical-align: top
                font-size: 12px
                color: rgb(7, 17, 27)
              .star
                display: inline-block
                margin: 0 12px
                vertical-align: top
              .score
                display: inline-block
                line-height: 18px
                vertical-align: top
                font-size: 12px
                color: rgb(255, 153, 0)
            .delivery-wrapper
              font-size: 0
              .title
                line-height: 18px
                font-size: 12px
                color: rgb(7, 17, 27)
              .delivery
                margin-left: 12px
                font-size: 12px
                color: rgb(147, 153, 159)
    </style>
    View Code

    rating组件开发(四)

    继续编写ratings.vue组件,实现评论区内容展示

    <template>
      <div class="ratings">
        <div class="ratings-content">
          <div class="overview">
            <div class="overview-left">
              <h1 class="score">{{seller.score}}</h1>
              <div class="title">综合评分</div>
              <div class="rank">高于周边商家{{seller.rankRate}}%</div>
            </div>
            <div class="overview-right">
              <div class="score-wrapper">
                <span class="title">服务态度</span>
                <star :size="36" :score="seller.serviceScore"></star>
                <span class="score">{{seller.serviceScore}}</span>
              </div>
              <div class="score-wrapper">
                <span class="title">商品评分</span>
                <star :size="36" :score="seller.foodScore"></star>
                <span class="score">{{seller.foodScore}}</span>
              </div>
              <div class="delivery-wrapper">
                <span class="title">送达时间</span>
                <span class="delivery">{{seller.deliveryTime}}分钟</span>
              </div>
            </div>
          </div>
          <split></split>
          <ratingselect :selectType="selectType" :onlyContent="onlyContent" :ratings="ratings"></ratingselect>
          <div class="rating-wrapper">
            <ul>
              <li v-for="rating in ratings" class="rating-item">
                <div class="avatar">
                  <img width="28" height="28" :src="rating.avatar">
                </div>
                <div class="content">
                  <h1 class="name">{{rating.username}}</h1>
                  <div class="star-wrapper">
                    <star :size="24" :score="rating.score"></star>
                    <span class="delivery" v-show="rating.deliveryTime">{{rating.deliveryTime}}</span>
                  </div>
                  <p class="text">{{rating.text}}</p>
                  <div class="recommend" v-show="rating.recommend && rating.recommend.length">
                    <span class="icon-thumb_up"></span>
                    <span class="item" v-for="item in rating.recommend">{{item}}</span>
                  </div>
                  <div class="time">
                    {{rating.rateTime}}
                  </div>
                </div>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      import star from '../../components/star/star';
      import split from '../../components/split/split';
      import ratingselect from '../../components/ratingselect/ratingselect';
    
      const ALL = 2;
      const ERR_OK = 0;
      export default {
        name: 'v-ratings',
        props: {
          seller: {
            type: Object
          }
        },
        data() {
          return {
            ratings: [],
            selectType: ALL,
            onlyContent: true
          };
        },
        created() {
          this.$http.get('/api/ratings').then((response) => {
            response = response.body;
            if (response.errno === ERR_OK) {
              this.ratings = response.data;
            }
          });
        },
        components: {
          star,
          split,
          ratingselect
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      .ratings
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          display: flex
          padding: 18px 0
          .overview-left
            flex: 0 0 137px
            padding: 6px 0
             137px
            border-right: 1px solid rgba(7, 17, 27, 0.1)
            text-align: center
            @media only screen and (max- 320px)
              flex: 0 0 120px
               120px
            .score
              margin-bottom: 6px
              line-height: 28px
              font-size: 24px
              color: rgb(255, 153, 0)
            .title
              margin-bottom: 8px
              line-height: 12px
              font-size: 12px
              color: rgb(7, 17, 27)
            .rank
              line-height: 10px
              font-size: 10px
              color: rgb(147, 153, 159)
          .overview-right
            flex: 1
            padding: 6px 0 6px 24px
            @media only screen and (max- 320px)
              padding-left: 6px
            .score-wrapper
              margin-bottom: 8px
              font-size: 0
              .title
                display: inline-block
                line-height: 18px
                vertical-align: top
                font-size: 12px
                color: rgb(7, 17, 27)
              .star
                display: inline-block
                margin: 0 12px
                vertical-align: top
              .score
                display: inline-block
                line-height: 18px
                vertical-align: top
                font-size: 12px
                color: rgb(255, 153, 0)
            .delivery-wrapper
              font-size: 0
              .title
                line-height: 18px
                font-size: 12px
                color: rgb(7, 17, 27)
              .delivery
                margin-left: 12px
                font-size: 12px
                color: rgb(147, 153, 159)
    </style>
    View Code

    rating组件开发(五)

    继续编写ratings.vue组件,实现评论区滚动功能和相关样式

    <template>
      <div class="ratings" ref="ratings">
        <div class="ratings-content">
          <div class="overview">
            <div class="overview-left">
              <h1 class="score">{{seller.score}}</h1>
              <div class="title">综合评分</div>
              <div class="rank">高于周边商家{{seller.rankRate}}%</div>
            </div>
            <div class="overview-right">
              <div class="score-wrapper">
                <span class="title">服务态度</span>
                <star :size="36" :score="seller.serviceScore"></star>
                <span class="score">{{seller.serviceScore}}</span>
              </div>
              <div class="score-wrapper">
                <span class="title">商品评分</span>
                <star :size="36" :score="seller.foodScore"></star>
                <span class="score">{{seller.foodScore}}</span>
              </div>
              <div class="delivery-wrapper">
                <span class="title">送达时间</span>
                <span class="delivery">{{seller.deliveryTime}}分钟</span>
              </div>
            </div>
          </div>
          <split></split>
          <ratingselect :selectType="selectType" :onlyContent="onlyContent" :ratings="ratings"></ratingselect>
          <div class="rating-wrapper">
            <ul>
              <li v-for="rating in ratings" class="rating-item">
                <div class="avatar">
                  <img width="28" height="28" :src="rating.avatar">
                </div>
                <div class="content">
                  <h1 class="name">{{rating.username}}</h1>
                  <div class="star-wrapper">
                    <star :size="24" :score="rating.score"></star>
                    <span class="delivery" v-show="rating.deliveryTime">{{rating.deliveryTime}}</span>
                  </div>
                  <p class="text">{{rating.text}}</p>
                  <div class="recommend" v-show="rating.recommend && rating.recommend.length">
                    <span class="icon-thumb_up"></span>
                    <span class="item" v-for="item in rating.recommend">{{item}}</span>
                  </div>
                  <div class="time">
                    {{rating.rateTime | formatDate}}
                  </div>
                </div>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import star from '../../components/star/star';
      import split from '../../components/split/split';
      import ratingselect from '../../components/ratingselect/ratingselect';
      import { formatDate } from '../../common/js/date';
    
      const ALL = 2;
      const ERR_OK = 0;
      export default {
        name: 'v-ratings',
        props: {
          seller: {
            type: Object
          }
        },
        data() {
          return {
            ratings: [],
            selectType: ALL,
            onlyContent: true
          };
        },
        created() {
          this.$http.get('/api/ratings').then((response) => {
            response = response.body;
            if (response.errno === ERR_OK) {
              this.ratings = response.data;
              this.$nextTick(() => {
                this.scroll = new BScroll(this.$refs.ratings, {
                  click: true
                });
              });
            }
          });
        },
        filters: {
          formatDate(time) {
            let date = new Date(time);
            return formatDate(date, 'yyyy-MM-dd hh:mm');
          }
        },
        components: {
          star,
          split,
          ratingselect
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"
      .ratings
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          display: flex
          padding: 18px 0
          .overview-left
            flex: 0 0 137px
            padding: 6px 0
             137px
            border-right: 1px solid rgba(7, 17, 27, 0.1)
            text-align: center
            @media only screen and (max- 320px)
              flex: 0 0 120px
               120px
            .score
              margin-bottom: 6px
              line-height: 28px
              font-size: 24px
              color: rgb(255, 153, 0)
            .title
              margin-bottom: 8px
              line-height: 12px
              font-size: 12px
              color: rgb(7, 17, 27)
            .rank
              line-height: 10px
              font-size: 10px
              color: rgb(147, 153, 159)
          .overview-right
            flex: 1
            padding: 6px 0 6px 24px
            @media only screen and (max- 320px)
              padding-left: 6px
            .score-wrapper
              margin-bottom: 8px
              font-size: 0
              .title
                display: inline-block
                line-height: 18px
                vertical-align: top
                font-size: 12px
                color: rgb(7, 17, 27)
              .star
                display: inline-block
                margin: 0 12px
                vertical-align: top
              .score
                display: inline-block
                line-height: 18px
                vertical-align: top
                font-size: 12px
                color: rgb(255, 153, 0)
            .delivery-wrapper
              font-size: 0
              .title
                line-height: 18px
                font-size: 12px
                color: rgb(7, 17, 27)
              .delivery
                margin-left: 12px
                font-size: 12px
                color: rgb(147, 153, 159)
        .rating-wrapper
          padding: 0 18px
          .rating-item
            display: flex
            padding: 18px 0
            border-1px(rgba(7, 17, 27, 0.1))
            .avatar
              flex: 0 0 28px
               28px
              margin-right: 12px
              img
                border-radius: 50%
            .content
              position: relative
              flex: 1
              .name
                margin-bottom: 4px
                line-height: 12px
                font-size: 10px
                color: rgb(7, 17, 27)
              .star-wrapper
                margin-bottom: 6px
                font-size: 0
                .star
                  display: inline-block
                  margin-right: 6px
                  vertical-align: top
                .delivery
                  display: inline-block
                  vertical-align: top
                  line-height: 12px
                  font-size: 10px
                  color: rgb(147, 153, 159)
              .text
                margin-bottom: 8px
                line-height: 18px
                color: rgb(7, 17, 27)
                font-size: 12px
              .recommend
                line-height: 16px
                font-size: 0
                .icon-thumb_up, .item
                  display: inline-block
                  margin: 0 8px 4px 0
                  font-size: 9px
                .icon-thumb_up
                  color: rgb(0, 160, 220)
                .item
                  padding: 0 6px
                  border: 1px solid rgba(7, 17, 27, 0.1)
                  border-radius: 1px
                  color: rgb(147, 153, 159)
                  background: #fff
              .time
                position: absolute
                top: 0
                right: 0
                line-height: 12px
                font-size: 10px
                color: rgb(147, 153, 159)
    </style>
    View Code

    rating组件开发(六)

    完善ratings.vue组件切换“满意、不满意”的效果(失败)

    <template>
      <div class="ratings" ref="ratings">
        <div class="ratings-content">
          <div class="overview">
            <div class="overview-left">
              <h1 class="score">{{seller.score}}</h1>
              <div class="title">综合评分</div>
              <div class="rank">高于周边商家{{seller.rankRate}}%</div>
            </div>
            <div class="overview-right">
              <div class="score-wrapper">
                <span class="title">服务态度</span>
                <star :size="36" :score="seller.serviceScore"></star>
                <span class="score">{{seller.serviceScore}}</span>
              </div>
              <div class="score-wrapper">
                <span class="title">商品评分</span>
                <star :size="36" :score="seller.foodScore"></star>
                <span class="score">{{seller.foodScore}}</span>
              </div>
              <div class="delivery-wrapper">
                <span class="title">送达时间</span>
                <span class="delivery">{{seller.deliveryTime}}分钟</span>
              </div>
            </div>
          </div>
          <split></split>
          <ratingselect :selectType="selectType" :onlyContent="onlyContent" :ratings="ratings"></ratingselect>
          <div class="rating-wrapper">
            <ul>
              <li v-for="rating in ratings" v-show="needShow(rating.rateType, rating.text)" class="rating-item">
                <div class="avatar">
                  <img width="28" height="28" :src="rating.avatar">
                </div>
                <div class="content">
                  <h1 class="name">{{rating.username}}</h1>
                  <div class="star-wrapper">
                    <star :size="24" :score="rating.score"></star>
                    <span class="delivery" v-show="rating.deliveryTime">{{rating.deliveryTime}}</span>
                  </div>
                  <p class="text">{{rating.text}}</p>
                  <div class="recommend" v-show="rating.recommend && rating.recommend.length">
                    <span class="icon-thumb_up"></span>
                    <span class="item" v-for="item in rating.recommend">{{item}}</span>
                  </div>
                  <div class="time">
                    {{rating.rateTime | formatDate}}
                  </div>
                </div>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import star from '../../components/star/star';
      import split from '../../components/split/split';
      import ratingselect from '../../components/ratingselect/ratingselect';
      import { formatDate } from '../../common/js/date';
    
      const ALL = 2;
      const ERR_OK = 0;
      export default {
        name: 'v-ratings',
        props: {
          seller: {
            type: Object
          }
        },
        data() {
          return {
            ratings: [],
            selectType: ALL,
            onlyContent: true
          };
        },
        created() {
          this.$http.get('/api/ratings').then((response) => {
            response = response.body;
            if (response.errno === ERR_OK) {
              this.ratings = response.data;
              this.$nextTick(() => {
                this.scroll = new BScroll(this.$refs.ratings, {
                  click: true
                });
              });
            }
          });
        },
        events: {
            'ratingtype.select'(type) {
                this.selectType = type;
                this.$nextTick(() => {
                    this.scroll.refresh();
                });
            },
            'content.toggle'(onlyContent) {
                this.onlyContent = onlyContent;
                this.$nextTick(() => {
                    this.scroll.refresh();
                });
            }
        },
        filters: {
          formatDate(time) {
            let date = new Date(time);
            return formatDate(date, 'yyyy-MM-dd hh:mm');
          }
        },
        methods: {
            needShow(type, text) {
            if (this.onlyContent && !text) {
              return false;
            }
            if (this.selectType === ALL) {
              return true;
            } else {
              return type === this.selectType;
            }
          }
        },
        components: {
          star,
          split,
          ratingselect
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"
      .ratings
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          display: flex
          padding: 18px 0
          .overview-left
            flex: 0 0 137px
            padding: 6px 0
             137px
            border-right: 1px solid rgba(7, 17, 27, 0.1)
            text-align: center
            @media only screen and (max- 320px)
              flex: 0 0 120px
               120px
            .score
              margin-bottom: 6px
              line-height: 28px
              font-size: 24px
              color: rgb(255, 153, 0)
            .title
              margin-bottom: 8px
              line-height: 12px
              font-size: 12px
              color: rgb(7, 17, 27)
            .rank
              line-height: 10px
              font-size: 10px
              color: rgb(147, 153, 159)
          .overview-right
            flex: 1
            padding: 6px 0 6px 24px
            @media only screen and (max- 320px)
              padding-left: 6px
            .score-wrapper
              margin-bottom: 8px
              font-size: 0
              .title
                display: inline-block
                line-height: 18px
                vertical-align: top
                font-size: 12px
                color: rgb(7, 17, 27)
              .star
                display: inline-block
                margin: 0 12px
                vertical-align: top
              .score
                display: inline-block
                line-height: 18px
                vertical-align: top
                font-size: 12px
                color: rgb(255, 153, 0)
            .delivery-wrapper
              font-size: 0
              .title
                line-height: 18px
                font-size: 12px
                color: rgb(7, 17, 27)
              .delivery
                margin-left: 12px
                font-size: 12px
                color: rgb(147, 153, 159)
        .rating-wrapper
          padding: 0 18px
          .rating-item
            display: flex
            padding: 18px 0
            border-1px(rgba(7, 17, 27, 0.1))
            .avatar
              flex: 0 0 28px
               28px
              margin-right: 12px
              img
                border-radius: 50%
            .content
              position: relative
              flex: 1
              .name
                margin-bottom: 4px
                line-height: 12px
                font-size: 10px
                color: rgb(7, 17, 27)
              .star-wrapper
                margin-bottom: 6px
                font-size: 0
                .star
                  display: inline-block
                  margin-right: 6px
                  vertical-align: top
                .delivery
                  display: inline-block
                  vertical-align: top
                  line-height: 12px
                  font-size: 10px
                  color: rgb(147, 153, 159)
              .text
                margin-bottom: 8px
                line-height: 18px
                color: rgb(7, 17, 27)
                font-size: 12px
              .recommend
                line-height: 16px
                font-size: 0
                .icon-thumb_up, .item
                  display: inline-block
                  margin: 0 8px 4px 0
                  font-size: 9px
                .icon-thumb_up
                  color: rgb(0, 160, 220)
                .item
                  padding: 0 6px
                  border: 1px solid rgba(7, 17, 27, 0.1)
                  border-radius: 1px
                  color: rgb(147, 153, 159)
                  background: #fff
              .time
                position: absolute
                top: 0
                right: 0
                line-height: 12px
                font-size: 10px
                color: rgb(147, 153, 159)
    </style>
    View Code

    seller组件开发(一)

    编写seller.vue组件,建立初步的头部结构

    <template>
      <div class="seller">
        <div class="seller-content">
          <div class="overview">
            <h1 class="title">{{seller.name}}</h1>
            <div class="desc">
              <star :size="36" :score="seller.score"></star>
              <span class="text">({{seller.ratingCount}})</span>
              <span class="text">月售{{seller.sellCount}}单</span>
            </div>
            <ul class="remark">
              <li class="block">
                <h2>起送价</h2>
                <div class="content">
                  <span class="stress">{{seller.minPrice}}元</span>
                </div>
              </li>
              <li class="block">
                <h2>商家配送</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryPrice}}元</span>
                </div>
              </li>
              <li class="block">
                <h2>平均配送时间</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryTime}}分钟</span>
                </div>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      import star from '../../components/star/star';
      export default {
        name: 'v-seller',
        props: {
          seller: {
            type: Object
          }
        },
        components: {
          star
        }
      }
    </script>
    
    <style scoped>
    
    </style>
    View Code

    seller组件开发(二)

    编写seller.vue组件,建立初步的头部结构样式

    <template>
      <div class="seller">
        <div class="seller-content">
          <div class="overview">
            <h1 class="title">{{seller.name}}</h1>
            <div class="desc border-1px">
              <star :size="36" :score="seller.score"></star>
              <span class="text">({{seller.ratingCount}})</span>
              <span class="text">月售{{seller.sellCount}}单</span>
            </div>
            <ul class="remark">
              <li class="block">
                <h2>起送价</h2>
                <div class="content">
                  <span class="stress">{{seller.minPrice}}元</span>
                </div>
              </li>
              <li class="block">
                <h2>商家配送</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryPrice}}元</span>
                </div>
              </li>
              <li class="block">
                <h2>平均配送时间</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryTime}}分钟</span>
                </div>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      import star from '../../components/star/star';
      export default {
        name: 'v-seller',
        props: {
          seller: {
            type: Object
          }
        },
        components: {
          star
        }
      }
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"
      .seller
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          position: relative
          padding: 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
          .desc
            padding-bottom: 18px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 0
            .star
              display: inline-block
              margin-right: 8px
              vertical-align: top
            .text
              display: inline-block
              margin-right: 12px
              line-height: 18px
              vertical-align: top
              font-size: 10px
              color: rgb(77, 85, 93)
          .remark
            display: flex
            padding-top: 18px
            .block
              flex: 1
              text-align: center
              border-right: 1px solid rgba(7, 17, 27, 0.1)
              &:last-child
                border: none
              h2
                margin-bottom: 4px
                line-height: 10px
                font-size: 10px
                color: rgb(147, 153, 159)
              .content
                line-height: 24px
                font-size: 10px
                color: rgb(7, 17, 27)
                .stress
                  font-size: 24px
    </style>
    View Code

    seller组件开发(三)

    编写seller.vue组件,公告与活动初次开发

    <template>
      <div class="seller">
        <div class="seller-content">
          <div class="overview">
            <h1 class="title">{{seller.name}}</h1>
            <div class="desc border-1px">
              <star :size="36" :score="seller.score"></star>
              <span class="text">({{seller.ratingCount}})</span>
              <span class="text">月售{{seller.sellCount}}单</span>
            </div>
            <ul class="remark">
              <li class="block">
                <h2>起送价</h2>
                <div class="content">
                  <span class="stress">{{seller.minPrice}}</span></div>
              </li>
              <li class="block">
                <h2>商家配送</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryPrice}}</span></div>
              </li>
              <li class="block">
                <h2>平均配送时间</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryTime}}</span>分钟
                </div>
              </li>
            </ul>
          </div>
          <split></split>
          <div class="bulletin">
            <h1 class="title">公告与活动</h1>
            <div class="content-wrapper border-1px">
              <p class="content">{{seller.bulletin}}</p>
            </div>
            <ul v-if="seller.supports" class="supports">
              <li class="support-item border-1px" 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>
        </div>
      </div>
    </template>
    
    <script>
      import star from '../../components/star/star';
      import split from '../../components/split/split';
      export default {
        name: 'v-seller',
        props: {
          seller: {
            type: Object
          }
        },
        components: {
          star,
          split
        },
        created() {
          this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
        }
      }
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"
      .seller
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          position: relative
          padding: 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
            font-weight: 700
          .desc
            padding-bottom: 18px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 0
            .star
              display: inline-block
              margin-right: 8px
              vertical-align: top
            .text
              display: inline-block
              margin-right: 12px
              line-height: 18px
              vertical-align: top
              font-size: 10px
              color: rgb(77, 85, 93)
          .remark
            display: flex
            padding-top: 18px
            .block
              flex: 1
              text-align: center
              border-right: 1px solid rgba(7, 17, 27, 0.1)
              &:last-child
                border: none
              h2
                margin-bottom: 4px
                line-height: 10px
                font-size: 10px
                color: rgb(147, 153, 159)
              .content
                line-height: 24px
                font-size: 10px
                color: rgb(7, 17, 27)
                .stress
                  font-size: 24px
        .bulletin
          padding: 18px 18px 0 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
          .content-wrapper
            padding: 0 12px 16px 12px
            border-1px(rgba(7, 17, 27, 0.1))
            .content
              line-height: 24px
              font-size: 12px
              color: rgb(240, 20, 20)
    </style>
    View Code

    seller组件开发(四)

    编写seller.vue组件,公告与活动二次开发,并且实现滚动功能

    <template>
      <div class="seller" ref="seller">
        <div class="seller-content">
          <div class="overview">
            <h1 class="title">{{seller.name}}</h1>
            <div class="desc border-1px">
              <star :size="36" :score="seller.score"></star>
              <span class="text">({{seller.ratingCount}})</span>
              <span class="text">月售{{seller.sellCount}}单</span>
            </div>
            <ul class="remark">
              <li class="block">
                <h2>起送价</h2>
                <div class="content">
                  <span class="stress">{{seller.minPrice}}</span></div>
              </li>
              <li class="block">
                <h2>商家配送</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryPrice}}</span></div>
              </li>
              <li class="block">
                <h2>平均配送时间</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryTime}}</span>分钟
                </div>
              </li>
            </ul>
          </div>
          <split></split>
          <div class="bulletin">
            <h1 class="title">公告与活动</h1>
            <div class="content-wrapper border-1px">
              <p class="content">{{seller.bulletin}}</p>
            </div>
            <ul v-if="seller.supports" class="supports">
              <li class="support-item border-1px" 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>
        </div>
      </div>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import star from '../../components/star/star';
      import split from '../../components/split/split';
      export default {
        name: 'v-seller',
        props: {
          seller: {
            type: Object
          }
        },
        components: {
          star,
          split
        },
        watch: {
          'seller' () {
            this.$nextTick(() => {
              this._initScroll();
            });
          }
        },
        methods: {
          _initScroll() {
            if (!this.scroll) {
              this.scroll = new BScroll(this.$refs.seller, {
                click: true
              });
            } else {
              this.scroll.refresh();
            }
          }
        },
        created() {
          this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
        }
      }
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"  
      .seller
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          position: relative
          padding: 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
            font-weight: 700
          .desc
            padding-bottom: 18px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 0
            .star
              display: inline-block
              margin-right: 8px
              vertical-align: top
            .text
              display: inline-block
              margin-right: 12px
              line-height: 18px
              vertical-align: top
              font-size: 10px
              color: rgb(77, 85, 93)
          .remark
            display: flex
            padding-top: 18px
            .block
              flex: 1
              text-align: center
              border-right: 1px solid rgba(7, 17, 27, 0.1)
              &:last-child
                border: none
              h2
                margin-bottom: 4px
                line-height: 10px
                font-size: 10px
                color: rgb(147, 153, 159)
              .content
                line-height: 24px
                font-size: 10px
                color: rgb(7, 17, 27)
                .stress
                  font-size: 24px
        .bulletin
          padding: 18px 18px 0 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
          .content-wrapper
            padding: 0 12px 16px 12px
            border-1px(rgba(7, 17, 27, 0.1))
            .content
              line-height: 24px
              font-size: 12px
              color: rgb(240, 20, 20)
          .supports
            .support-item
              padding: 16px 12px
              border-1px(rgba(7, 17, 27, 0.1))
              font-size: 0
              &:last-child
                border-none()
            .icon
              display: inline-block
               16px
              height: 16px
              vertical-align: top
              margin-right: 6px
              background-size: 16px 16px
              background-repeat: no-repeat
              &.decrease
                bg-image('decrease_4')
              &.discount
                bg-image('discount_4')
              &.guarantee
                bg-image('guarantee_4')
              &.invoice
                bg-image('invoice_4')
              &.special
                bg-image('special_4')
            .text
              line-height: 16px
              font-size: 12px
              color: rgb(7, 17, 27)
    </style>
    View Code

    seller组件开发——商家实景图(五)

    编写seller.vue组件,实现商家实景图效果

    <template>
      <div class="seller" ref="seller">
        <div class="seller-content">
          <div class="overview">
            <h1 class="title">{{seller.name}}</h1>
            <div class="desc border-1px">
              <star :size="36" :score="seller.score"></star>
              <span class="text">({{seller.ratingCount}})</span>
              <span class="text">月售{{seller.sellCount}}单</span>
            </div>
            <ul class="remark">
              <li class="block">
                <h2>起送价</h2>
                <div class="content">
                  <span class="stress">{{seller.minPrice}}</span></div>
              </li>
              <li class="block">
                <h2>商家配送</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryPrice}}</span></div>
              </li>
              <li class="block">
                <h2>平均配送时间</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryTime}}</span>分钟
                </div>
              </li>
            </ul>
          </div>
          <split></split>
          <div class="bulletin">
            <h1 class="title">公告与活动</h1>
            <div class="content-wrapper border-1px">
              <p class="content">{{seller.bulletin}}</p>
            </div>
            <ul v-if="seller.supports" class="supports">
              <li class="support-item border-1px" 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>
          <split></split>
          <div class="pics">
            <h1 class="title">商家实景</h1>
            <div class="pic-wrapper" ref="picWrapper">
              <ul class="pic-list" ref="picList">
                <li class="pic-item" v-for="pic in seller.pics">
                  <img :src="pic" width="120" height="90">
                </li>
              </ul>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import star from '../../components/star/star';
      import split from '../../components/split/split';
      export default {
        name: 'v-seller',
        props: {
          seller: {
            type: Object
          }
        },   
        created() {
          this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
        },
        watch: {
          'seller' () {
            this.$nextTick(() => {
              this._initScroll();
              this._initPics();
            });
          }
        },
        mounted() {
          this.$nextTick(() => {
            this._initScroll();
            this._initPics();
          });
        },
        methods: {
          _initScroll() {
            if (!this.scroll) {
              this.scroll = new BScroll(this.$refs.seller, {
                click: true
              });
            } else {
              this.scroll.refresh();
            }
          },
          _initPics() {
            if (this.seller.pics) {
              let picWidth = 120;
              let margin = 6;
              let width = (picWidth + margin) * this.seller.pics.length - margin;
              this.$refs.picList.style.width = width + 'px';
              this.$nextTick(() => {
                if (!this.picScroll) {
                  this.picScroll = new BScroll(this.$refs.picWrapper, {
                    scrollX: true,
                    eventPassthrough: 'vertical'
                  });
                } else {
                  this.picScroll.refresh();
                }
              });
            }
          }
        },
        components: {
          star,
          split
        }
      }
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"  
      .seller
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          position: relative
          padding: 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
            font-weight: 700
          .desc
            padding-bottom: 18px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 0
            .star
              display: inline-block
              margin-right: 8px
              vertical-align: top
            .text
              display: inline-block
              margin-right: 12px
              line-height: 18px
              vertical-align: top
              font-size: 10px
              color: rgb(77, 85, 93)
          .remark
            display: flex
            padding-top: 18px
            .block
              flex: 1
              text-align: center
              border-right: 1px solid rgba(7, 17, 27, 0.1)
              &:last-child
                border: none
              h2
                margin-bottom: 4px
                line-height: 10px
                font-size: 10px
                color: rgb(147, 153, 159)
              .content
                line-height: 24px
                font-size: 10px
                color: rgb(7, 17, 27)
                .stress
                  font-size: 24px
        .bulletin
          padding: 18px 18px 0 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
          .content-wrapper
            padding: 0 12px 16px 12px
            border-1px(rgba(7, 17, 27, 0.1))
            .content
              line-height: 24px
              font-size: 12px
              color: rgb(240, 20, 20)
          .supports
            .support-item
              padding: 16px 12px
              border-1px(rgba(7, 17, 27, 0.1))
              font-size: 0
              &:last-child
                border-none()
            .icon
              display: inline-block
               16px
              height: 16px
              vertical-align: top
              margin-right: 6px
              background-size: 16px 16px
              background-repeat: no-repeat
              &.decrease
                bg-image('decrease_4')
              &.discount
                bg-image('discount_4')
              &.guarantee
                bg-image('guarantee_4')
              &.invoice
                bg-image('invoice_4')
              &.special
                bg-image('special_4')
            .text
              line-height: 16px
              font-size: 12px
              color: rgb(7, 17, 27)
        .pics
          padding: 18px
          .title
            margin-bottom: 12px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
          .pic-wrapper
             100%
            overflow: hidden
            white-space: nowrap
            .pic-list
              font-size: 0
              .pic-item
                display: inline-block
                margin-right: 6px
                 120px
                height: 90px
                &:last-child
                  margin: 0
    </style>
    View Code

    seller组件开发——商家信息(六)

    编写seller.vue组件,实现商家信息部分

    <template>
      <div class="seller" ref="seller">
        <div class="seller-content">
          <div class="overview">
            <h1 class="title">{{seller.name}}</h1>
            <div class="desc border-1px">
              <star :size="36" :score="seller.score"></star>
              <span class="text">({{seller.ratingCount}})</span>
              <span class="text">月售{{seller.sellCount}}单</span>
            </div>
            <ul class="remark">
              <li class="block">
                <h2>起送价</h2>
                <div class="content">
                  <span class="stress">{{seller.minPrice}}</span></div>
              </li>
              <li class="block">
                <h2>商家配送</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryPrice}}</span></div>
              </li>
              <li class="block">
                <h2>平均配送时间</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryTime}}</span>分钟
                </div>
              </li>
            </ul>
          </div>
          <split></split>
          <div class="bulletin">
            <h1 class="title">公告与活动</h1>
            <div class="content-wrapper border-1px">
              <p class="content">{{seller.bulletin}}</p>
            </div>
            <ul v-if="seller.supports" class="supports">
              <li class="support-item border-1px" 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>
          <split></split>
          <div class="pics">
            <h1 class="title">商家实景</h1>
            <div class="pic-wrapper" ref="picWrapper">
              <ul class="pic-list" ref="picList">
                <li class="pic-item" v-for="pic in seller.pics">
                  <img :src="pic" width="120" height="90">
                </li>
              </ul>
            </div>
          </div>
          <split></split>
          <div class="info">
            <h1 class="title border-1px">商家信息</h1>
            <ul>
              <li class="info-item" v-for="info in seller.infos">{{info}}</li>
            </ul>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import star from '../../components/star/star';
      import split from '../../components/split/split';
      export default {
        name: 'v-seller',
        props: {
          seller: {
            type: Object
          }
        },   
        created() {
          this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
        },
        watch: {
          'seller' () {
            this.$nextTick(() => {
              this._initScroll();
              this._initPics();
            });
          }
        },
        mounted() {
          this.$nextTick(() => {
            this._initScroll();
            this._initPics();
          });
        },
        methods: {
          _initScroll() {
            if (!this.scroll) {
              this.scroll = new BScroll(this.$refs.seller, {
                click: true
              });
            } else {
              this.scroll.refresh();
            }
          },
          _initPics() {
            if (this.seller.pics) {
              let picWidth = 120;
              let margin = 6;
              let width = (picWidth + margin) * this.seller.pics.length - margin;
              this.$refs.picList.style.width = width + 'px';
              this.$nextTick(() => {
                if (!this.picScroll) {
                  this.picScroll = new BScroll(this.$refs.picWrapper, {
                    scrollX: true,
                    eventPassthrough: 'vertical'
                  });
                } else {
                  this.picScroll.refresh();
                }
              });
            }
          }
        },
        components: {
          star,
          split
        }
      }
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"  
      .seller
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          position: relative
          padding: 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
            font-weight: 700
          .desc
            padding-bottom: 18px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 0
            .star
              display: inline-block
              margin-right: 8px
              vertical-align: top
            .text
              display: inline-block
              margin-right: 12px
              line-height: 18px
              vertical-align: top
              font-size: 10px
              color: rgb(77, 85, 93)
          .remark
            display: flex
            padding-top: 18px
            .block
              flex: 1
              text-align: center
              border-right: 1px solid rgba(7, 17, 27, 0.1)
              &:last-child
                border: none
              h2
                margin-bottom: 4px
                line-height: 10px
                font-size: 10px
                color: rgb(147, 153, 159)
              .content
                line-height: 24px
                font-size: 10px
                color: rgb(7, 17, 27)
                .stress
                  font-size: 24px
        .bulletin
          padding: 18px 18px 0 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
          .content-wrapper
            padding: 0 12px 16px 12px
            border-1px(rgba(7, 17, 27, 0.1))
            .content
              line-height: 24px
              font-size: 12px
              color: rgb(240, 20, 20)
          .supports
            .support-item
              padding: 16px 12px
              border-1px(rgba(7, 17, 27, 0.1))
              font-size: 0
              &:last-child
                border-none()
            .icon
              display: inline-block
               16px
              height: 16px
              vertical-align: top
              margin-right: 6px
              background-size: 16px 16px
              background-repeat: no-repeat
              &.decrease
                bg-image('decrease_4')
              &.discount
                bg-image('discount_4')
              &.guarantee
                bg-image('guarantee_4')
              &.invoice
                bg-image('invoice_4')
              &.special
                bg-image('special_4')
            .text
              line-height: 16px
              font-size: 12px
              color: rgb(7, 17, 27)
        .pics
          padding: 18px
          .title
            margin-bottom: 12px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
          .pic-wrapper
             100%
            overflow: hidden
            white-space: nowrap
            .pic-list
              font-size: 0
              .pic-item
                display: inline-block
                margin-right: 6px
                 120px
                height: 90px
                &:last-child
                  margin: 0
        .info
          padding: 18px 18px 0 18px
          color: rgb(7, 17, 27)
          .title
            padding-bottom: 12px
            line-height: 14px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 14px
          .info-item
            padding: 16px 12px
            line-height: 16px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 12px
            &:last-child
              border-none()
    </style>
    View Code

    seller组件开发——收藏商家(一)

    编写seller.vue组件,实现商家收藏基本效果

    <template>
      <div class="seller" ref="seller">
        <div class="seller-content">
          <div class="overview">
            <h1 class="title">{{seller.name}}</h1>
            <div class="desc border-1px">
              <star :size="36" :score="seller.score"></star>
              <span class="text">({{seller.ratingCount}})</span>
              <span class="text">月售{{seller.sellCount}}单</span>
            </div>
            <ul class="remark">
              <li class="block">
                <h2>起送价</h2>
                <div class="content">
                  <span class="stress">{{seller.minPrice}}</span></div>
              </li>
              <li class="block">
                <h2>商家配送</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryPrice}}</span></div>
              </li>
              <li class="block">
                <h2>平均配送时间</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryTime}}</span>分钟
                </div>
              </li>
            </ul>
            <div class="favorite" @click="toggleFavorite">
              <span class="icon-favorite" :class="{'active':favorite}"></span>
              <span class="text">{{favoriteText}}</span>
            </div>
          </div>
          <split></split>
          <div class="bulletin">
            <h1 class="title">公告与活动</h1>
            <div class="content-wrapper border-1px">
              <p class="content">{{seller.bulletin}}</p>
            </div>
            <ul v-if="seller.supports" class="supports">
              <li class="support-item border-1px" 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>
          <split></split>
          <div class="pics">
            <h1 class="title">商家实景</h1>
            <div class="pic-wrapper" ref="picWrapper">
              <ul class="pic-list" ref="picList">
                <li class="pic-item" v-for="pic in seller.pics">
                  <img :src="pic" width="120" height="90">
                </li>
              </ul>
            </div>
          </div>
          <split></split>
          <div class="info">
            <h1 class="title border-1px">商家信息</h1>
            <ul>
              <li class="info-item" v-for="info in seller.infos">{{info}}</li>
            </ul>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import star from '../../components/star/star';
      import split from '../../components/split/split';
      export default {
        name: 'v-seller',
        props: {
          seller: {
            type: Object
          }
        },
        data() {
          return {
            favorite: false
          };
        },
        computed: {
          favoriteText() {
            return this.favorite ? '已收藏' : '收藏';
          }
        },
        created() {
          this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
        },
        watch: {
          'seller' () {
            this.$nextTick(() => {
              this._initScroll();
              this._initPics();
            });
          }
        },
        mounted() {
          this.$nextTick(() => {
            this._initScroll();
            this._initPics();
          });
        },
        methods: {
            toggleFavorite(event) {
            if (!event._constructed) {
              return;
            }
            this.favorite = !this.favorite;
          },
          _initScroll() {
            if (!this.scroll) {
              this.scroll = new BScroll(this.$refs.seller, {
                click: true
              });
            } else {
              this.scroll.refresh();
            }
          },
          _initPics() {
            if (this.seller.pics) {
              let picWidth = 120;
              let margin = 6;
              let width = (picWidth + margin) * this.seller.pics.length - margin;
              this.$refs.picList.style.width = width + 'px';
              this.$nextTick(() => {
                if (!this.picScroll) {
                  this.picScroll = new BScroll(this.$refs.picWrapper, {
                    scrollX: true,
                    eventPassthrough: 'vertical'
                  });
                } else {
                  this.picScroll.refresh();
                }
              });
            }
          }
        },
        components: {
          star,
          split
        }
      }
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"  
      .seller
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          position: relative
          padding: 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
            font-weight: 700
          .desc
            padding-bottom: 18px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 0
            .star
              display: inline-block
              margin-right: 8px
              vertical-align: top
            .text
              display: inline-block
              margin-right: 12px
              line-height: 18px
              vertical-align: top
              font-size: 10px
              color: rgb(77, 85, 93)
          .remark
            display: flex
            padding-top: 18px
            .block
              flex: 1
              text-align: center
              border-right: 1px solid rgba(7, 17, 27, 0.1)
              &:last-child
                border: none
              h2
                margin-bottom: 4px
                line-height: 10px
                font-size: 10px
                color: rgb(147, 153, 159)
              .content
                line-height: 24px
                font-size: 10px
                color: rgb(7, 17, 27)
                .stress
                  font-size: 24px
          .favorite
            position: absolute
             50px
            right: 11px
            top: 18px
            text-align: center
            .icon-favorite
              display: block
              margin-bottom: 4px
              line-height: 24px
              font-size: 24px
              color: #d4d6d9
              &.active
                color: rgb(240, 20, 20)
            .text
              line-height: 10px
              font-size: 10px
              color: rgb(77, 85, 93)
        .bulletin
          padding: 18px 18px 0 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
          .content-wrapper
            padding: 0 12px 16px 12px
            border-1px(rgba(7, 17, 27, 0.1))
            .content
              line-height: 24px
              font-size: 12px
              color: rgb(240, 20, 20)
          .supports
            .support-item
              padding: 16px 12px
              border-1px(rgba(7, 17, 27, 0.1))
              font-size: 0
              &:last-child
                border-none()
            .icon
              display: inline-block
               16px
              height: 16px
              vertical-align: top
              margin-right: 6px
              background-size: 16px 16px
              background-repeat: no-repeat
              &.decrease
                bg-image('decrease_4')
              &.discount
                bg-image('discount_4')
              &.guarantee
                bg-image('guarantee_4')
              &.invoice
                bg-image('invoice_4')
              &.special
                bg-image('special_4')
            .text
              line-height: 16px
              font-size: 12px
              color: rgb(7, 17, 27)
        .pics
          padding: 18px
          .title
            margin-bottom: 12px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
          .pic-wrapper
             100%
            overflow: hidden
            white-space: nowrap
            .pic-list
              font-size: 0
              .pic-item
                display: inline-block
                margin-right: 6px
                 120px
                height: 90px
                &:last-child
                  margin: 0
        .info
          padding: 18px 18px 0 18px
          color: rgb(7, 17, 27)
          .title
            padding-bottom: 12px
            line-height: 14px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 14px
          .info-item
            padding: 16px 12px
            line-height: 16px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 12px
            &:last-child
              border-none()
    </style>
    View Code

    seller组件开发——收藏商家(二)

    步骤一:编写App.vue根组件,为传递商家ID值做前期准备

    <template>
      <div>
        <v-header :seller="seller"></v-header>
        <div class="tab border-1px">
          <div class="tab-item">
              <router-link to="/goods">商品</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/ratings">评价</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/seller">商家</router-link>
          </div>
        </div>
        <router-view :seller="seller"></router-view>
      </div>
    </template>
    
    <script>
      import header from './components/header/header.vue';
      import {urlParse} from './common/js/util';
      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) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.seller = Object.assign({}, this.seller, response.data);
              }
              this.seller = response.data;
            })
        },
        components: {
          'v-header': header
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "./common/stylus/mixin.styl";
      .tab
        display: flex
         100%
        height: 40px
        line-height: 40px
        /*border-bottom: 1px solid rgba(7, 17, 27, 0.1)*/
        border-1px(rgba(7, 17, 27, 0.1))
        .tab-item
          flex: 1
          text-align: center
          & > a
           display:block
           font-size:16px
           color:rgb(77,85,93)
           &.active
            color:rgb(240,20,20)
    </style>
    View Code

    步骤二:新建util.js文件,实现查询的效果

    /**
     * 解析url参数
     * @example ?id=12345&a=b
     * @return Object {id:12345,a:b}
     */
    export function urlParse() {
      let url = window.location.search;
      let obj = {};
      let reg = /[?&][^?&]+=[^?&]+/g;
      let arr = url.match(reg);
      // ['?id=12345', '&a=b']
      if (arr) {
        arr.forEach((item) => {
          let tempArr = item.substring(1).split('=');
          let key = decodeURIComponent(tempArr[0]);
          let val = decodeURIComponent(tempArr[1]);
          obj[key] = val;
        });
      }
      return obj;
    };
    View Code

    seller组件开发——收藏商家(三)

    步骤一:新建store.js文件,实现存储状态的功能

    export function saveToLocal(id, key, value) {
      let seller = window.localStorage.__seller__;
      if (!seller) {
        seller = {};
        seller[id] = {};
      } else {
        seller = JSON.parse(seller);
        if (!seller[id]) {
          seller[id] = {};
        }
      }
      seller[id][key] = value;
      window.localStorage.__seller__ = JSON.stringify(seller);
    };
    
    export function loadFromLocal(id, key, def) {
      let seller = window.localStorage.__seller__;
      if (!seller) {
        return def;
      }
      seller = JSON.parse(seller)[id];
      if (!seller) {
        return def;
      }
      let ret = seller[key];
      return ret || def;
    };
    View Code

    步骤二:编写seller.vue组件,将数据传入store.js中

    <template>
      <div class="seller" ref="seller">
        <div class="seller-content">
          <div class="overview">
            <h1 class="title">{{seller.name}}</h1>
            <div class="desc border-1px">
              <star :size="36" :score="seller.score"></star>
              <span class="text">({{seller.ratingCount}})</span>
              <span class="text">月售{{seller.sellCount}}单</span>
            </div>
            <ul class="remark">
              <li class="block">
                <h2>起送价</h2>
                <div class="content">
                  <span class="stress">{{seller.minPrice}}</span></div>
              </li>
              <li class="block">
                <h2>商家配送</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryPrice}}</span></div>
              </li>
              <li class="block">
                <h2>平均配送时间</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryTime}}</span>分钟
                </div>
              </li>
            </ul>
            <div class="favorite" @click="toggleFavorite">
              <span class="icon-favorite" :class="{'active':favorite}"></span>
              <span class="text">{{favoriteText}}</span>
            </div>
          </div>
          <split></split>
          <div class="bulletin">
            <h1 class="title">公告与活动</h1>
            <div class="content-wrapper border-1px">
              <p class="content">{{seller.bulletin}}</p>
            </div>
            <ul v-if="seller.supports" class="supports">
              <li class="support-item border-1px" 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>
          <split></split>
          <div class="pics">
            <h1 class="title">商家实景</h1>
            <div class="pic-wrapper" ref="picWrapper">
              <ul class="pic-list" ref="picList">
                <li class="pic-item" v-for="pic in seller.pics">
                  <img :src="pic" width="120" height="90">
                </li>
              </ul>
            </div>
          </div>
          <split></split>
          <div class="info">
            <h1 class="title border-1px">商家信息</h1>
            <ul>
              <li class="info-item" v-for="info in seller.infos">{{info}}</li>
            </ul>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      import BScroll from 'better-scroll';
      import star from '../../components/star/star';
      import split from '../../components/split/split';
      import {saveToLocal, loadFromLocal} from '../../common/js/store';
      export default {
        name: 'v-seller',
        props: {
          seller: {
            type: Object
          }
        },
        data() {
          return {
            favorite: (() => {
              return loadFromLocal(this.seller.id, 'favorite', false);
            })()
          };
        },
        computed: {
          favoriteText() {
            return this.favorite ? '已收藏' : '收藏';
          }
        },
        created() {
          this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
        },
        watch: {
          'seller' () {
            this.$nextTick(() => {
              this._initScroll();
              this._initPics();
            });
          }
        },
        mounted() {
          this.$nextTick(() => {
            this._initScroll();
            this._initPics();
          });
        },
        methods: {
            toggleFavorite(event) {
            if (!event._constructed) {
              return;
            }
            this.favorite = !this.favorite;
            saveToLocal(this.seller.id, 'favorite', this.favorite);
          },
          _initScroll() {
            if (!this.scroll) {
              this.scroll = new BScroll(this.$refs.seller, {
                click: true
              });
            } else {
              this.scroll.refresh();
            }
          },
          _initPics() {
            if (this.seller.pics) {
              let picWidth = 120;
              let margin = 6;
              let width = (picWidth + margin) * this.seller.pics.length - margin;
              this.$refs.picList.style.width = width + 'px';
              this.$nextTick(() => {
                if (!this.picScroll) {
                  this.picScroll = new BScroll(this.$refs.picWrapper, {
                    scrollX: true,
                    eventPassthrough: 'vertical'
                  });
                } else {
                  this.picScroll.refresh();
                }
              });
            }
          }
        },
        components: {
          star,
          split
        }
      }
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      @import "../../common/stylus/mixin.styl"  
      .seller
        position: absolute
        top: 174px
        bottom: 0
        left: 0
         100%
        overflow: hidden
        .overview
          position: relative
          padding: 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
            font-weight: 700
          .desc
            padding-bottom: 18px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 0
            .star
              display: inline-block
              margin-right: 8px
              vertical-align: top
            .text
              display: inline-block
              margin-right: 12px
              line-height: 18px
              vertical-align: top
              font-size: 10px
              color: rgb(77, 85, 93)
          .remark
            display: flex
            padding-top: 18px
            .block
              flex: 1
              text-align: center
              border-right: 1px solid rgba(7, 17, 27, 0.1)
              &:last-child
                border: none
              h2
                margin-bottom: 4px
                line-height: 10px
                font-size: 10px
                color: rgb(147, 153, 159)
              .content
                line-height: 24px
                font-size: 10px
                color: rgb(7, 17, 27)
                .stress
                  font-size: 24px
          .favorite
            position: absolute
             50px
            right: 11px
            top: 18px
            text-align: center
            .icon-favorite
              display: block
              margin-bottom: 4px
              line-height: 24px
              font-size: 24px
              color: #d4d6d9
              &.active
                color: rgb(240, 20, 20)
            .text
              line-height: 10px
              font-size: 10px
              color: rgb(77, 85, 93)
        .bulletin
          padding: 18px 18px 0 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
          .content-wrapper
            padding: 0 12px 16px 12px
            border-1px(rgba(7, 17, 27, 0.1))
            .content
              line-height: 24px
              font-size: 12px
              color: rgb(240, 20, 20)
          .supports
            .support-item
              padding: 16px 12px
              border-1px(rgba(7, 17, 27, 0.1))
              font-size: 0
              &:last-child
                border-none()
            .icon
              display: inline-block
               16px
              height: 16px
              vertical-align: top
              margin-right: 6px
              background-size: 16px 16px
              background-repeat: no-repeat
              &.decrease
                bg-image('decrease_4')
              &.discount
                bg-image('discount_4')
              &.guarantee
                bg-image('guarantee_4')
              &.invoice
                bg-image('invoice_4')
              &.special
                bg-image('special_4')
            .text
              line-height: 16px
              font-size: 12px
              color: rgb(7, 17, 27)
        .pics
          padding: 18px
          .title
            margin-bottom: 12px
            line-height: 14px
            color: rgb(7, 17, 27)
            font-size: 14px
          .pic-wrapper
             100%
            overflow: hidden
            white-space: nowrap
            .pic-list
              font-size: 0
              .pic-item
                display: inline-block
                margin-right: 6px
                 120px
                height: 90px
                &:last-child
                  margin: 0
        .info
          padding: 18px 18px 0 18px
          color: rgb(7, 17, 27)
          .title
            padding-bottom: 12px
            line-height: 14px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 14px
          .info-item
            padding: 16px 12px
            line-height: 16px
            border-1px(rgba(7, 17, 27, 0.1))
            font-size: 12px
            &:last-child
              border-none()
    </style>
    View Code

    体验优化

    编写App.vue组件实现状态的保留,原理是将当前的状态保存在内存中,使用的keep-alive标签。

    <template>
      <div>
        <v-header :seller="seller"></v-header>
        <div class="tab border-1px">
          <div class="tab-item">
              <router-link to="/goods">商品</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/ratings">评价</router-link>
          </div>
          <div class="tab-item">
              <router-link to="/seller">商家</router-link>
          </div>
        </div>
        <keep-alive>
          <router-view :seller="seller"></router-view>
        </keep-alive>
      </div>
    </template>
    
    <script>
      import header from './components/header/header.vue';
      import {urlParse} from './common/js/util';
      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) => {
              response = response.body;
              if(response.error === ERR_OK){
                  this.seller = Object.assign({}, this.seller, response.data);
              }
              this.seller = response.data;
            })
        },
        components: {
          'v-header': header
        }
      }
    </script>
    
    <style scoped lang="stylus" rel="stylesheet/stylus">
      @import "./common/stylus/mixin.styl";
      .tab
        display: flex
         100%
        height: 40px
        line-height: 40px
        /*border-bottom: 1px solid rgba(7, 17, 27, 0.1)*/
        border-1px(rgba(7, 17, 27, 0.1))
        .tab-item
          flex: 1
          text-align: center
          & > a
           display:block
           font-size:16px
           color:rgb(77,85,93)
           &.active
            color:rgb(240,20,20)
    </style>
    View Code
  • 相关阅读:
    Idea快捷键
    Java学习之路--书籍推荐
    泵式等待基元
    uni-app,wex5,APPcan,ApiCloud几款国内webapp开发框架的选型对比
    前端框架2019 云开发
    select2 javascript控件 如何设置指定的值
    Github 索引
    linux
    WPF 中的 Uri 地址的不同写法
    WPF GridSplitter 使用技巧
  • 原文地址:https://www.cnblogs.com/fengxiongZz/p/8214130.html
Copyright © 2020-2023  润新知