电商项目笔记2(Home组件)

Home组件拆分

1. 业务流程

  • 静态页面(样式)
  • 拆分静态组件
  • 发请求获取服务器数据进行展示
  • 开发动态业务 拆分组件:结构+样式+图片资源    一共要拆分为七个组件

2. 三级联动组件-全局组件的使用(重要)

由于在search、detail、Home组件都有使用,所以设置为全局组件,只需要注册一次,就可以在项目任意地方使用

  • 在Home下创建/Home/TypeNav/index.vue:结构、样式、资源

  • 全局组件的引入和注册(在main.js)

    `
     //引入全局组件TypeNav,注册为全局组件,在Home种直接使用,不用再引入
    import TypeNav from "@/pages/Home/TypeNav"
    //第一个参数:全局组件的名字。第二个参数:哪一个组件
    Vue.component(TypeNav.name,TypeNav)
    `
    

3. 其他组件

  • 创建/Home/ListContainer/index.vue组件:结构、样式、资源

  • 不是全局组件,要再Home/index.vue下引入和注册

    ```
    <template>
      <div>
    <!--    home-->
    <!--    直接使用全局组件TypeNav不用注册(main.js已经注册过)-->
        <TypeNav></TypeNav>
       <ListContainer/>
      </div>
    </template>
    
    <script>
    //首先引入Home下的非全局组件ListContainer
    import ListContainer from "@/pages/Home/ListContainer"
    
    export default {
      name: "index.vue",
    
      //然后进行ListContainer注册,components的s不要忘记
      components:{
        ListContainer
      }
    }
    </script>
    ```
    
  • 在Home/index.vue中的Template标签使用

4. axios二次封装

  • 安装axios:
    进入项目目录:cnpm install --save axios

  • 新建src/api/request.js
    `

    //此文件对axios进行二次封装
    
    //引入axios
    import axios from "axios";
    
    //1.利用axios对象 的方法create 创建一个axios实例
    //requests就是axios,只不过稍微配置下
    const requests = axios.create(
        //create方法可以传配置对象
        {
            //基础路径,发请求时路径中会出现api
            baseURL:"http://api ",
            timeout :5000
        }
    
    )
    
    //请求拦截器:在发请求之前,请求拦截器可以检测到,可以在发请求之前做一些事
    requests.interceptors.request.use(
        //config:配置对象,对象里面有一个属性很重要,header请求头
        (config)=>{
            return config;
        }
    )
    
    //响应拦截器
    requests.interceptors.response.use(
        (res)=>{
            //成功的回调函数,服务器响应数据回来以后,响应拦截器可以检测到,做一些事情
                return res.data
        },
        (error)=>{
            //响应失败的回调函数
            return Promise.reject(new Error('fail'))
        }
    )
    
    //将axios对象 requests对外暴露,否则无法使用
    export default requests;
    `
    

5. 接口统一管理

项目小:完全可以在组件的生命周期中发请求
项目大:上述方法axio.get(XXX)等,如果接口地址改变则需一个一个修改,所以接口需要统一管理

  1. 新建/src/api/index.js

    //当前文件:所有api接口统一管理
    //引入requests
    import requests from "@/api/request";
    
    //三级联动接口
    ///api/product/getBaseCategoryList   get 无参数
    
    
    
    //对外export一个函数,别的模块发请求 可以拿到函数接口,通过调用来发请求
    export const reqCategoryList = ()=>{
        //axios实例requests来发请求,且返回requests返回值,否则返回undefined。 axios发请求返回结果是Promised对象
        return requests(
            {
                url:'/product/getBaseCategoryList ',
                method:'get'
            }
        )
    }
    
  2. 在main.js测试reqCategoryList

    //入口文件测试reqCategoryList函数 因为分别暴露则要{}
    import {reqCategoryList} from "@/api"
    reqCategoryList();
    

    报错(跨域问题)

  3. 跨域问题的解决
    >跨域问题:协议、域名、端口号不同的请求为跨域问题
    服务器与服务器没有跨域问题,前端由于浏览器和浏览器才有跨域问题
    前台项目在发请求时,只要请求路径中出现了/api,代理服务器会找真实的后端服务器要数据,返回数据给前端进行展示
    前端项目本地服务器:http://localhost:8080/#/home
    后端服务器:http://39.98.123.211
    解决:通过JSONP、CROS、代理(webpack提供)

    代理(webpack提供)
    webpack.config.js相当于webpack的说明书,也可以在vue.config.js中配置

    //vue.config.js相当于vue的说明书,注意配置完成后需要重启服务器生效
    
    module.exports={
    //关闭eslint校验
        lintOnSave:false,
    
        //代理跨域
        devServer: {
            proxy:{
                //前台项目在发请求时,只要请求路径中出现了/api(也可以是其他名称设置),代理服务器会找真实的后端服务器请求数据
                "/api":{
                    //要代理的服务器
                    target:"http://39.98.123.211",
                    //因为真是接口中已经带有/api了,所以路径不需要重写
                    // pathRewrite:{"^/api":""}
                }
            }
    
        },
    }
    
  4. 进度条设置:nprogress模块实现进度条功能 工作的时候,,修改nprogress.css来修改进度条的颜色样式

    
    //引入进度条
    import nprogress from "nprogress"
    //引入进度条样式
    import "nprogress/nprogress.css"
    //start 进度条开始,done 进度条结束
    
    

6. Vuex

介绍:[https://www.jianshu.com/p/120eaf50331c]

  • 新建/src/store/index.js
    `

    import Vue from "vue";
    import Vuex from 'vuex';
    
    //需要使用插件一次
    Vue.use(Vuex);
    const state = {};//仓库处理数据的地方
    const mutations = {};//修改state数据唯一的手段
    const actions = {};//处理action,可以书写自己的业务逻辑,也可以处理异步
    const getters ={}//理解为计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便
    
    //Vuex是一个对象,store是其方法,是构造函数,可以初始化Vuex仓库
    //对外暴露store类的一个实例
    export default new Vuex.Store({
        // state:{},
        // mutations:{},
        // actions:{},
        // getters:{}
        state,
        mutations,
        actions,
        getters
        }
    );
    
    `
    
  • store插件与路由插件一样,需要在main.js引入和注册

7. 插件使用规则小结 (目前使用过vue-router,vuex)

  1. 下载插件

    npm install --save XXX@版本号   
    
    

    注意与vue2对应版本

  2. 创建目录/src/XXX/index.js(配置文件)

    //1.导入相应的包
    import Vue from "vue";
    import XXX from '';
    
    **//2.需要使用插件一次**
    Vue.use(XXX);
    
    //3.对外暴露
    export default new XX({
        
    });
    
  3. main.js

    index.js相当于node的一个模块,须要让文件执行一次 即在main.js入口文件引入和注册

    //1.引入
    import    XXX from ""  
    //2.注册
    new Vue({
      render: h => h(App),
      //3.注册,kv一致省略v,注册后非路由组件和路由组件上都有$XXX属性
      XXX
    }).$mount('#app')
    

8. vuex模块化开发:modules

  1. 为每一个模块设置一个仓库方便管理 原始:计数器案例

          ```
          //mutations:修改state数据唯一的手段
          const mutations = {
              ADD(state) {
                  state.count++;
              },
              DROP(state){
                  state.count--;
              }
          };
    
          //处理action,可以书写自己的业务逻辑,也可以处理异步
          const actions = {
              add({commit}){
                  commit('ADD')
              },
              drop({commit}){
                  commit('DROP')
              }
          };
    
          ```
    
  2. 新建src/store/home/index.js,即给Home模块新建一个自己的仓库

    import Vue from "vue";
    import Vuex from "vuex";
    Vue.use(Vuex)
    const state = {};
    const mutations = {};
    const actions = {};
    const getters = {}
    
    export default new Vuex.Store({
        state,
        mutations,
        actions,
        getters
    })
    
    
     ![](https://img2022.cnblogs.com/blog/2717738/202203/2717738-20220308101811328-517078300.png)
    

9. 三级联动组件业务

三级联动数据展示业务
  1. TypeNav/index.vue 组件

    <script>
    import {mapState} from "vuex";
    export default {
      name: "TypeNav",
      //组件挂载完毕,可以向服务器发请求
      mounted() {
        //通知Vuex发请求,获取数据,存储于仓库中
        //注意这个名字home/categoryList
        this.$store.dispatch("home/categoryList")
      },
      computed:{
        ...mapState(
            //右侧需要的是一个函数。当使用这个计算属性的时候,右侧函数立即会执行一次
            //注入一个参数state,这个state是大仓库中的数据
            {
              categoryList:state=>state.home.categoryList
            }
        )
      }
    }
    </script>
    
    
  2. store/home/index.js home仓库

    actions通过调用API接口函数reqCategoryList(),向服务器发请求,获取服务器返回的result,若成功返回,返回数据为result.data
    mutations中的CATEGORYLIST将数据赋给state中定义数组categoryList

    import {reqCategoryList} from "@/api";
    
    //home模块的小仓库
    const state = {
        //state中默认的初始值不能瞎写,根据接口返回值来初始化
        categoryList:[],
    };
    
    const mutations = {
        CATEGORYLIST(state,data){
            state.categoryList = data;
        }
    };
    
    const actions = {
        //通过API接口函数调用reqCategoryList(),向服务器发请求,获取服务器的数据
        async categoryList({commit}){
            let result = await reqCategoryList();
            if (result.code===200){
                commit("CATEGORYLIST",result.data)
            }
    }}
    const getters = {};
    
    
    export default {
        namespaced: true,
        state,
        mutations,
        actions,
        getters
    }
    
    
  3. TypeNav/index.vue

           <div class="all-sort-list2">
    <div class="item" v-for="(c1,index) in categoryList":key="c1.categoryId">
      <h3 >
        <a href="">{{c1.categoryName}}</a>
      </h3>
      <div class="item-list clearfix">
        <div class="subitem" v-for="(c2,index) in c1.categoryChild":key="c2.categoryId">
          <dl class="fore">
            <dt>
              <a href="">{{c2.categoryName}}</a>
            </dt>
            <dd>
              <em v-for="(c3,index) in c2.categoryChild":key="c3.categoryId">
                <a href="">{{c3.categoryName}}</a>
              </em>
            </dd>
          </dl>
        </div>
      </div>
    </div>
    </div>
    
三级联动设置 鼠标悬浮改变背景颜色
显示二三级分类
解决卡顿现象:引入防抖与节流
  `
      highlight:throttle(
          function(index){
              this.currentIndex = index;
          },50
      ),
  `

完成三级联动路由跳转search 并传递参数业务

  1. 路由跳转使用编程式导航

    <template>
      <div>
        <!-- 商品分类导航 -->
        <div class="type-nav">
          <div class="container">
            <h2 class="all" >全部商品分类</h2>
            <nav class="nav">
              <a href="###">服装城</a>
              <a href="###">美妆馆</a>
              <a href="###">尚品汇超市</a>
              <a href="###">全球购</a>
              <a href="###">闪购</a>
              <a href="###">团购</a>
              <a href="###">有趣</a>
              <a href="###">秒杀</a>
            </nav>
            <div class="sort" >
              <!--        设置鼠标离开后不再选中该分类,鼠标点击开始查找-->
              <div class="all-sort-list2" @click="goSearch" @mouseleave="leave()">
                <div class="item" v-for="(c1,index) in categoryList":key="c1.categoryId" :class='{cur:index==currentIndex}'>
    <!--              设置鼠标进入c1标签内高亮显示-->
                  <h3  @mouseenter="highlight(index)" >
    <!--               a标签 href要删去,否则跳转后仍然跳回Home,同时给a标签添加自定义属性name和Id-->
                    <a :data-categoryName="c1.categoryName":data-category1Id = "c1.categoryId">{{c1.categoryName}}</a>
                  </h3>
                  <div class="item-list clearfix" :style="{display:currentIndex==index?'block':'none'}">
                    <div class="subitem" v-for="(c2,index) in c1.categoryChild":key="c2.categoryId">
                      <dl class="fore">
    <!--                    显示二级分类标签-->
                        <dt>
                          <a :data-categoryName="c2.categoryName" :data-category2Id = "c2.categoryId">{{c2.categoryName}}</a>
                        </dt>
    <!--                    显示三级分类-->
                        <dd>
                          <em v-for="(c3,index) in c2.categoryChild":key="c3.categoryId">
                            <a :data-categoryName="c3.categoryName" :data-category3Id = "c3.categoryId">{{c3.categoryName}}</a>
                          </em>
                        </dd>
                      </dl>
                    </div>
                  </div>
                </div>
                </div>
                </div>
      </div>
        </div>
      </div>
    </template>
    
  2. methods

     ```
     methods:{
    
       // 引入节流
       //throttle函数回调函数别用箭头函数。可能出现上下文this问题
       highlight:throttle(
           function(index){
               this.currentIndex = index;
           },50
       ),
       //鼠标移出
       leave(){
         this.currentIndex =-1;
       },
    
       goSearch(event)
       {
         let element = event.target;
         let {categoryname,category1id,category2id,category3id} = element.dataset;
         //整理路由跳转的参数
         //最后应该是this.$router.push(name:"search",query:{categoryName:'xxx',categoryId:'sss'})
         if(categoryname)
         {
           let location = {name:"search"};//不论几级都要带name参数
           let query = {categoryName:categoryname}
           if(category1id){
             query.category1Id = category1id;
           }
           else if (category2id){
             query.category2Id = category2id;
    
           }
           else {
             query.category3Id = category3id;
           }
           //将location和query合并:给Location动态添加参数query
           location.query = query;
           //进行路由的跳转
           this.$router.push(location);
         }
    
       }
    
     }
    
    
    

完成三级联动在search中动态显示,并添加动画

  1. 分类的显示与隐藏:v-show:true
  2. 挂载完毕后分类条目在home组件显示,在search组件中隐藏
    mounted() { //通知Vuex发请求,获取数据,存储于仓库中 //注意这个名字要带路径 // this.$store.commit("/home/categoryList") this.$store.dispatch("home/categoryList") //判断当前页面在home还是search ,从而决定typenav是显示还是隐藏 if (this.$route.path != "/home") //注意this. this.show = false; },
  3. 鼠标移动到“全部商品分类”时,显示分类条目;移出时,隐藏分类条目
    **注意在哪个类上进行绑定动作
  4. 在search组件中显示与隐藏,在home组件一直显示
  5. 显示和隐藏过渡动画添加

完成三级联动优化

目前还存在一个问题可以优化,即每次search时,home模块就会销毁,同时typenav组件也消失,创建search的typenav组件时时就会再次创建typenav并挂载,而每次挂载typenav组件时会执行 this.$store.dispatch("home/categoryList"),执行reqCategoryList()函数向服务器获取三级联动的数据,而数据是固定的,获取一次后不会改变,实际却获取多次会影响性能,因此要让reqCategoryList()只执行一次

  1. 放在本地的想法:仍会多次请求,只不过从服务器转移到了本地,且还要判断
  2. 利用根组件App.vue的特性:mouted 只会挂载一次
  3. 能否在人口文件main.js中使用?不能,this.$store只有组件有,在main.js中 this是undefined

合并参数:合并query和paramas参数(???)

  • 在三家联动组件点击时传递了query参数,在搜索按钮点击时传递的是paramas参数,应该让两个动作传递的参数统一

home首页 listContainer|Floor组件业务:mock模拟假数据流程

  1. 安装依赖包mockjs
  2. 在src文件夹下创建一个文件夹,mock文件夹。
  3. 准备模拟的数据 把mock数据需要的图片放置于public文件夹中!*webpack打包时放入dist文件夹中 比如:listContainer中的轮播图的数据 [ {id:1,imgUrl:'xxxxxxxxx'}, {id:2,imgUrl:'xxxxxxxxx'}, {id:3,imgUrl:'xxxxxxxxx'}, ]
  4. 在mock文件夹中创建一个server.js文件 注意:在server.js文件当中引入json文件 banner.json||floor.json的数据没有进行暴露,但是可以在server模块中使用。 因为对于webpack当中一些模块:图片、json,不需要对外暴露,因为默认就是对外暴露。
  5. 通过mock模块模拟出数据
    通过Mock.mock方法进行模拟数据
  6. 回到入口文件,引入serve.js mock需要的数据|相关mock代码页书写完毕,关于mock当中serve.js需要执行一次, 如果不执行,和没有书写一样的。
  7. 在API文件夹中创建mockRequest【axios实例:baseURL:'/mock'】 专门获取模拟数据用的axios实例。

获取mock 中的banner轮播图数据并显示在Listcontainer中

  1. 原来的/src/api/request.js是对服务器发Ajax请求,获取数据 现在是获取mock 的数据,因此新建/api/mockRequest.js
  2. 在/api/index.js进行mockRequest.js的统一管理
  3. Listcontainer组件挂载时,向store请求数据,则store下进行三连环操作
  4. ...mapstate

轮播图:swiper快速开发轮播图

  • 安装swiper @5
  • Swiper使用步骤:
    第一步:引入依赖包【swiper.js|swiper.css】
    第二步:静态页面中结构必须完整【container、wrap、slider】,类名不能瞎写
    第三步:初始化swiper实例

1:swiper在Vue项目中使用 (开发ListContainer组件【首页banner图片】) 提示:卸载插件,你可以不用删除node_modules文件夹,可以使用npm uninstall xxxx进行卸载
1.1swiper安装到项目当中
1.2在相应的组件引入swiper.js|swiper.css但是需要注意,home模块很多组件都使用到swiper.css,没必要在每一个组件内部都引入样式一次, 只需要在入口文件引入一次即可。
1.3:重要:初始化swiper实例在哪里书写? 初始化swiper实例之前,页面中的节点(结构)务必要有, 对于Vue一个组件而言,mounted[组件挂载完毕:相应的结构不就有了吗] mounted-->组件挂载完毕(不完全正确)
1.4动态效果为什么没有出来? Swiper需要获取到轮播图的节点DOM,才能给swiper轮播添加动态效果, 因为没有获取到节点。
Ajax请求是异步的,

watch+nextTick解决轮播图动态效果不展示问题

问题:初始化轮播图实例时,必须保证轮播图的数据和结构都已经存在(1)轮播图的数据bannerlist数据已经由空数组变为含四个图片对象的数组,即轮播图数据能获取到(2)v-for是建立轮播图结构的,遍历需要时间

  1. mouted中初始化:轮播图数据没有获取到
  2. updated中初始化: 能保证数据改变后(轮播图数据获取到)再初始化,如果组件有很多响应式(data),只要有一个属性值发生变化updated还会再次执行,再次初始化实例。
  3. 延迟器: 可以解决,但这种解决方案存在风险(无法确定用户请求到底需要多长时间),因此没办法确定 延迟器时间
  4. 单独的watch:可以保证轮播图数据已经获取,但是数据一获取就开始初始化轮播图实例,无法保证v-for遍历完成,出现结构

终极解决方案: watch+nextTick

获取floor组件mock数据

一般步骤

  1. 静态页面加载
  2. 仓库三连环操作存储数据(仓库中得有数据才能被请求到)
  3. 写请求仓库数据的api,触发仓库的action
  4. 组件获取数据进行展示

在哪里触发action?
bannnerList动作在Listcontainer组件中触发,但FloorList动作不能在Floor中触发,因为home组件中有两个Floor组件,是需要遍历到两个组件的数据
所以在Home组件中触发
1.注意数据的结构
2.:src
3.v-for循环比较复杂时可以直接用数组下标

Home首页轮播图拆分成一个全局组件

由于Home组件中有多个水品轮播图,(search中也有)所以可以复用; 很多组件公用的东西可以封装成全局组件

posted @   YL2022  阅读(128)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示