• 外卖webAPP(一)


    一,首页页面情况

     

    1.底部四个切换栏路由组件路由配置

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)
    // 引入路由组件
    import Msite from '@/views/Msite'
    import Order from '@/views/Order'
    import Profile from '@/views/Profile'
    import Search from '@/views/Search'
    
    const routes = [
      {
        path: '/msite',
        component: Msite
      },
      {
        path: '/order',
        component: Order
      },
      {
        path: '/profile',
        component: Profile
      },
      {
        path: '/search',
        component: Search
      },
      {
        path: '/',
        redirect: '/msite'
      }
    ]
    
    const router = new VueRouter({
      routes
    })
    
    export default router

    在app.vue入口组件引入路由

    <template>
      <div>
        <h3>app</h3>
        <router-view></router-view>
        <FooterGuide></FooterGuide>
      </div>
    </template>

    二,在footerGuider组件中

    2.1,在底部的切换栏标签中有个on类名,渲染颜色,我们需要点击底部的四个切换栏(导航式路由),然后颜色也会跟着变化(根据路由的路径来判断)

        <footer class="footer_guide border-1px">
        <a href="javascript:;" class="guide_item " :class="{on:$route.path ==='/msite'}" @click="goTo('/msite')">
          <span class="item_icon">
            <i class="iconfont icon-waimai"></i>
          </span>
          <span>外卖</span>
        </a>
        <a href="javascript:;" class="guide_item" :class="{on:$route.path ==='/search'}" @click="goTo('/search')">
          <span class="item_icon">
            <i class="iconfont icon-search"></i>
          </span>
          <span>搜索</span>
        </a>
        <a href="javascript:;" class="guide_item"  :class="{on:$route.path ==='/order'}"   @click="goTo('/order')">
          <span class="item_icon">
            <i class="iconfont icon-dingdan"></i>
          </span>
          <span>订单</span>
        </a>
        <a href="javascript:;" class="guide_item"  :class="{on:$route.path ==='/profile'}"   @click="goTo('/profile')">
          <span class="item_icon">
            <i class="iconfont icon-geren"></i>
          </span>
          <span>我的</span>
        </a>
      </footer>
      .guide_item
          display flex
          flex 1
          text-align center
          flex-direction column
          align-items center
          margin 5px
          color #999999
          &.on
            color blue
     // 点击底部切换栏,跳转到对应的路由组件 
        goTo(path){
          this.$router.replace(path)
        }

    三,定义公共headerTop组件

    首页,搜索,订单,我的这四个路由组件页面顶部是一样的,我们需要将header抽离出来定义公共组件,但是我们发现首页组件顶部左边多了一个搜索图标,右边多个登录|注册

    此时我们需要用到作用域插槽,子组件headerTop利用slot组件占位,父组件传入结构

    首页组件(miste)顶部样式

    搜索组件(search)顶部样式

    订单(order)顶部样式

    我的(profile)顶部样式

    3.1,定义公共组件HeaderTop,利用具名插槽

    <template>
      <header class="header">
        <!--插槽作用域,子组件占位,父组件传入结构 -->
        <slot name="left"></slot>
        <span class="header_title">
          <span class="header_title_text ellipsis"
            >{{title}}</span
          >
        </span>
        <!--插槽作用域,子组件占位,父组件传入结构 -->
        <slot name="right"></slot>
    
      </header>
    </template>

    接收父组件传递过来的属性

    export default {
      name:'HeaderTop',
      props:['title'],

    3.2,在首页msite组价,插入子组件,需要两个结构给子组件

        <HeaderTop title="昌平区北七家宏福科技园(337省道北)">
          <template>
            <span class="header_search" slot="left">
              <i class="iconfont icon-sousuo"></i>
            </span>
    
            <span class="header_login" slot="right">
              <span class="header_login_text">登录|注册</span>
            </span>
          </template>
        </HeaderTop>

     在搜索组件(search)插入子组件,不需要结构给子组件

    <HeaderTop title="搜索"></HeaderTop>

    在订单组件(order)插入子组件,不需要结构给子组件

     <HeaderTop title="订单列表"></HeaderTop>

    3.3,在msite首页组件添加轮播图插件swiper,

    1.安装5版本的swiper,  npm  i swiper@5  -S     目前最新是6,不稳定

    //swiper引入js和css
    // import Swiper from "swiper";
    // import "swiper/css/swiper.min.css";
    <div class="swiper-container">
            <div class="swiper-wrapper">
              
              <div class="swiper-slide">
                <a href="javascript:" class="link_to_food">
                  <div class="food_container">
                    <img src="./images/nav/9.jpg" />
                  </div>
                  <span>甜品饮品</span>
                </a>
                <a href="javascript:" class="link_to_food">
                  <div class="food_container">
                    <img src="./images/nav/10.jpg" />
                  </div>
                  <span>商超便利</span>
                </a>
                <a href="javascript:" class="link_to_food">
                  <div class="food_container">
                    <img src="./images/nav/11.jpg" />
                  </div>
                  <span>美食</span>
                </a>
                <a href="javascript:" class="link_to_food">
                  <div class="food_container">
                    <img src="./images/nav/12.jpg" />
                  </div>
                  <span>简餐</span>
                </a>
                <a href="javascript:" class="link_to_food">
                  <div class="food_container">
                    <img src="./images/nav/13.jpg" />
                  </div>
                  <span>新店特惠</span>
                </a>
                <a href="javascript:" class="link_to_food">
                  <div class="food_container">
                    <img src="./images/nav/14.jpg" />
                  </div>
                  <span>准时达</span>
                </a>
                <a href="javascript:" class="link_to_food">
                  <div class="food_container">
                    <img src="./images/nav/1.jpg" />
                  </div>
                  <span>预订早餐</span>
                </a>
                <a href="javascript:" class="link_to_food">
                  <div class="food_container">
                    <img src="./images/nav/2.jpg" />
                  </div>
                  <span>土豪推荐</span>
                </a>
              </div>
            </div>
            <!-- Add Pagination -->
            <div class="swiper-pagination"></div>
          </div>
     mounted() {
        new Swiper('.swiper-container', {
          loop: true, // 可以循环轮播
          // 如果需要分页器
          pagination: {
            el: '.swiper-pagination'
          }
        })
      }

    3.4,在msite首页组件拆分shoplist组件,html,css,图片拆分

    3.5,在我的组件(profile),点击登录|注册链接,跳转到登录页面

     

    配置登录路由组件对象

    import Login from '@/views/Login'
    
    const routes = [
      {
        path: '/msite',
        component: Msite,
        meta: {
          showFooterGuider: true
        }
      },
      {
        path: '/order',
        component: Order,
        meta: {
          showFooterGuider: true
        }
      },
      {
        path: '/profile',
        component: Profile,
        meta: {
          showFooterGuider: true
        }
      },
      {
        path: '/search',
        component: Search,
        meta: {
          showFooterGuider: true
        }
      },
      {
        path: '/',
        redirect: '/msite'
      },
      {
        path: '/login',
        component: Login,
        meta: {
          showFooterGuider: false
        }
      }
    ]
      <section class="profile-number">
            <!-- 路由链家登录 -->
            <router-link to="/login"  class="profile-link">
                <div class="profile_image">
                <i class="iconfont icon-person"></i>
              </div>
              <div class="user-info">
                <p class="user-info-top">登录/注册</p>
                <p>
                  <span class="user-icon">
                    <i class="iconfont icon-shouji icon-mobile"></i>
                  </span>
                  <span class="icon-mobile-number">暂无绑定手机号</span>
                </p>
              </div>
              <span class="arrow">
                <i class="iconfont icon-jiantou1"></i>
              </span>
            </router-link>

    两个细节问题

    1.在登录组件,点击左上角的返回图标,返回到我的组件,利用路由返回函数

           <button class="login_submit">登录</button>
              </form>
              <a href="javascript:;" class="about_us">关于我们</a>
            </div>
            <a href="javascript:" class="go_back" @click="$router.back()">
              <i class="iconfont icon-jiantou2"></i>
            </a>
          </div>

    2.在登录页面底部的四个切换栏需要影藏,在路由对象中配置meta属性,布尔值,然后在app组件中v-if判断下

    在app.vue中判断判断是否显示footer切换栏

    <template>
      <div>
        <router-view></router-view>
        <FooterGuide v-if="$route.meta.showFooterGuider"></FooterGuide>
      </div>
    </template>

    3.6,axios二次封装,接口函数书写以及发送请求

    新建api文件夹,新建ajax.js文件,axios二次封装 Ajax.js, 后台服务器地址http://localhost:4000

     
    // 对axios的二次封装
    import axios from 'axios'
    const service = axios.create({
     
      baseURL: 'http://localhost:4000',
      timeout: 2000
    })
    
    // 请求拦截器
    service.interceptors.request.use(config => {
      return config
    })
    
    // 响应拦截器
    service.interceptors.response.use(
      response => {
        return response.data
      },
      error => {
        alert('请求出错' + error.message || '未知错误')
        //以后不允许用户继续处理: 中断promise链
        return new Promise(() => {}) //返回pending状态的promise 中断
      }
    )
    
    export default service

    新建index.js文件,编写接口函数

    import ajax from './ajax.js'
    
    // 1、根据经纬度获取位置详情
    export const reqAddress = (geohash) => ajax.get(`/position/${geohash}`)
    // 2、获取食品分类列表
    export const reqFoodCategorys = () => ajax.get('/index_category')
    // 3、根据经纬度获取商铺列表
    export const reqShops = (longitude, latitude) => ajax.post('/shops', {params:{longitude, latitude}})
    // 4、根据经纬度和关键字搜索商铺列表
    export const reqSearchShop = (geohash, keyword) => ajax.post('/search_shops', {params:{geohash, keyword}})
    // 6、用户名密码登陆
    export const reqPwdLogin = ({name, pwd, captcha}) => ajax.post('/login_pwd', {name, pwd, captcha})
    // 7、发送短信验证码
    export const reqSendCode = (phone) => ajax.get('/sendcode', {params:{phone}})
    // 8、手机号验证码登陆
    export const reqSmsLogin = (phone, code) => ajax.post('/login_sms', {phone, code})
    // 9、根据会话获取用户信息
    export const reqUserInfo = () => ajax.get('/userinfo')
    // 10、用户登出
    export const reqLogout = () => ajax.get('/logout')
    
    /**
     * 获取商家信息
     */
    export const reqShopInfo = () => ajax.get('/info')
    
    /**
     * 获取商家评价数组
     */
    export const reqShopRatings = () => ajax.get('/ratings')
    
    /**
     * 获取商家商品数组
     */
    export const reqShopGoods = () => ajax.get('/goods')

    在main.js中测试接口数据

    import * as API from '@/api'
    API.reqAddress(('40.10038,116.36867')).then(r => {
      console.log(r)
    })

    此时,控制台报错了,请求跨域了,本机浏览器端口8080请求4000端口,我们需要在vue.config.js 配置代理proxy

    module.exports = {
      lintOnSave: false,
      devServer: {
        //需要转发路由的路径
        proxy: {
          '/api': {
            target: 'http://localhost:4000',
            pathRewrite: { '^/api': '' },
            changeOrigin: true
          }
        }
      }
    }

    然后在封装的ajax中修改下路径,此时再次请求数据,是OK的

    // 对axios的二次封装
    import axios from 'axios'
    const service = axios.create({
      baseURL: 'http://localhost:8080/api',
      // baseURL: 'http://localhost:4000',
      timeout: 2000
    })

    3.7,利用vuex,发送ajax,状态管理

    1.新建store文件夹,新建index.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    // 引入接口函数
    import {
      reqAddress,
      reqFoodCategorys,
      reqShops,
      reqUserInfo,
      reqLogout,
      reqShopRatings,
      reqShopGoods,
      reqShopInfo,
      reqSearchShop
    } from '@/api'
    
    const state = {
      latitude: 40.10038, // 纬度
      longitude: 116.36867, // 经度
      address: {}, //地址相关信息对象
    
    }
    
    const mutations = {
      RECEIVE_ADDRESS(state, address) {
        state.address = address
      },
    
    
    }
    
    const actions = {
      // 异步获取地址
      async getAddress({ commit, state }) {
        // 发送异步ajax请求
        const geohash = state.latitude + ',' + state.longitude
        const result = await reqAddress(geohash)
        // 提交一个mutation
        if (result.code === 0) {
          const address = result.data
          commit('RECEIVE_ADDRESS', address)
        }
      },
    
    
    
    }
    
    const getters = {}
    
    export default new Vuex.Store({
      state,
      mutations,
      actions,
      getters
    })

    在main.js中引入store,并注册

    import store from '@/store'
    
    Vue.config.productionTip = false
    
    new Vue({
      router,
      render: h => h(App),
      store
    }).$mount('#app')

    在msite首页组件中从store中获取数据

    除了dispatch到store中actions的函数,还可以用mapActions中触发

      mounted(){
        this.$store.dispatch('getAddress')
      },

    或者

    import { mapActions, mapState } from 'vuex'
      methods:{
        ...mapActions(['getAddress'])
      },
     mounted(){
        // this.$store.dispatch('getAddress')
        this.getAddress()
      },

    从store中的获取数据

    computed:{
    
        // address(){
        //   return this.$store.state.address
        // }
        ...mapState(['address'])
      },

    填充数据

    <HeaderTop :title="address.name">
          <template>
            <span class="header_search" slot="left">
              <i class="iconfont icon-sousuo"></i>
            </span>
    
            <span class="header_login" slot="right">
              <span class="header_login_text">登录|注册</span>
            </span>
          </template>
        </HeaderTop>

    3.8,在msite首页组件中的轮播图中获取动态数据,并填充数据

    注:这个轮播图,应该是是一个双重数组遍历,一维数组是有几页,二维数组是有八个数据

    然而我们后台返回的数据是一个数组数据,而不是一个二维数组。因此我们需要编写逻辑去实现两个数组嵌套

      mounted() {
    
        this.$store.dispatch('getCategorys')
      },
    computed: {
     
        ...mapState(['address', 'categorys']),

    返回的部分数据

    {
      "code": 0,
      data: [
              {
                id: 1,
                is_in_serving: true,
                description: "0元早餐0起送,每天都有新花样。",
                title: "预订早餐",
                link: "",
                image_url: "/d/49/7757ff22e8ab28e7dfa5f7e2c2692jpeg.jpeg",
                icon_url: "",
                title_color: "",
                __v: 0
              },
              {
                id: 65,
                is_in_serving: true,
                description: "",
                title: "土豪推荐",
                image_url: "/d/49/7757ff22e8ab28e7dfa5f7e2c2692jpeg.jpeg",
                link: "",
                icon_url: "",
                title_color: "",
                __v: 0
              },
              ... 共n条数据
            ]
    }
    // 发送请求返回的数据categorys是一个数组,但是看html,应该要双重遍历,是一个二维数组,将categorys
        // 遍历下,需要将每一项对象添加到嵌套的小数组中
        // 思路;1.需要定义一个大数组,小数组,将小数组嵌套在大数组中,小数组添加categorys的每一个对象
        // 2.小数组只能有八项对象(页面只有八项标签),然后添加一个新的小数组

    在computed中计算该数组,重要

    categorysArr() {
          // 定义大数组
          let arr = []
          // 定义小数组
          let minArr = []
          this.categorys.forEach(item => {
            // 如果当前小数组已经满了, 创建一个新的
            if (minArr.length === 8) {
              minArr = []
            }
    
            // 如果minArr是空的, 将小数组保存到大数组中
            if (minArr.length === 0) {
              arr.push(minArr)
            }
    
            // 将每一项对象添加到小数组中
            minArr.push(item)
          })
    
          return arr
        }
      }

    此时整理得到的数据

    将数据填充到模板中

      data() {
        return {
          baseImageUrl: 'https://fuss10.elemecdn.com'
        }
      },
     <div class="swiper-container">
            <div class="swiper-wrapper">
              <div
                class="swiper-slide"
                v-for="(c1, index) in categorysArr"
                :key="index"
              >
                <a
                  href="javascript:"
                  class="link_to_food"
                  v-for="(c2, index) in c1"
                  :key="c2.id"
                >
                  <div class="food_container">
                    <img :src="baseImageUrl + c2.image_url" />
                  </div>
                  <span>{{ c2.title }}</span>
                </a>
              </div>
            </div>

    此时有个bug,swiper轮播图并没有效果,如何有效果呢

    1.监视异步请求获取的数据categorys,

    2.此时数据回来了,但是页面还没有跟新,需要在下一次dom页面跟新完成执行

     watch: {
          categorys (value) { // categorys数组中有数据了, 在异步更新界面之前执行
            // 使用setTimeout可以实现效果, 但不是太好
            /*setTimeout(() => {
              // 创建一个Swiper实例对象, 来实现轮播
              new Swiper('.swiper-container', {
                loop: true, // 可以循环轮播
                // 如果需要分页器
                pagination: {
                  el: '.swiper-pagination',
                },
              })
            }, 100)*/
    
            // 界面更新就立即创建Swiper对象
            this.$nextTick(() => {// 一旦完成界面更新, 立即调用(此条语句要写在数据更新之后)
              // 创建一个Swiper实例对象, 来实现轮播
              new Swiper('.swiper-container', {
                loop: true, // 可以循环轮播
                // 如果需要分页器
                pagination: {
                  el: '.swiper-pagination',
                },
              })
    
            })
    
          }
        },
     
  • 相关阅读:
    C#基础:ref和out的区别
    .NET Petshop详解(五):petshop输出缓存设置
    静态方法和实例化方法的区别
    .Net Petshop详解(一):petshop概览和准备工作
    什么是MVC(三层架构)
    C# 反射入门知识
    C#类与对象
    linux用户权限的管理
    PHP pear安装
    shell神器curl用法笔记
  • 原文地址:https://www.cnblogs.com/fsg6/p/14302707.html
Copyright © 2020-2023  润新知