电商项目笔记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)等,如果接口地址改变则需一个一个修改,所以接口需要统一管理
-
新建/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' } ) }
-
在main.js测试reqCategoryList
//入口文件测试reqCategoryList函数 因为分别暴露则要{} import {reqCategoryList} from "@/api" reqCategoryList();
报错(跨域问题)
-
跨域问题的解决
>跨域问题:协议、域名、端口号不同的请求为跨域问题
服务器与服务器没有跨域问题,前端由于浏览器和浏览器才有跨域问题
前台项目在发请求时,只要请求路径中出现了/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":""} } } }, }
-
进度条设置: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)
-
下载插件
npm install --save XXX@版本号
注意与vue2对应版本
-
创建目录/src/XXX/index.js(配置文件)
//1.导入相应的包 import Vue from "vue"; import XXX from ''; **//2.需要使用插件一次** Vue.use(XXX); //3.对外暴露 export default new XX({ });
-
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
-
为每一个模块设置一个仓库方便管理 原始:计数器案例
``` //mutations:修改state数据唯一的手段 const mutations = { ADD(state) { state.count++; }, DROP(state){ state.count--; } }; //处理action,可以书写自己的业务逻辑,也可以处理异步 const actions = { add({commit}){ commit('ADD') }, drop({commit}){ commit('DROP') } }; ```
-
新建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 })

9. 三级联动组件业务
三级联动数据展示业务
-
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>
-
store/home/index.js home仓库
actions通过调用API接口函数reqCategoryList(),向服务器发请求,获取服务器返回的result,若成功返回,返回数据为result.data
mutations中的CATEGORYLIST将数据赋给state中定义数组categoryListimport {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 }
-
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 并传递参数业务
-
路由跳转使用编程式导航
<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>
-
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中动态显示,并添加动画
- 分类的显示与隐藏:v-show:true
- 挂载完毕后分类条目在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; },
- 鼠标移动到“全部商品分类”时,显示分类条目;移出时,隐藏分类条目
**注意在哪个类上进行绑定动作 - 在search组件中显示与隐藏,在home组件一直显示
- 显示和隐藏过渡动画添加
完成三级联动优化
目前还存在一个问题可以优化,即每次search时,home模块就会销毁,同时typenav组件也消失,创建search的typenav组件时时就会再次创建typenav并挂载,而每次挂载typenav组件时会执行 this.$store.dispatch("home/categoryList"),执行reqCategoryList()函数向服务器获取三级联动的数据,而数据是固定的,获取一次后不会改变,实际却获取多次会影响性能,因此要让reqCategoryList()只执行一次
- 放在本地的想法:仍会多次请求,只不过从服务器转移到了本地,且还要判断
- 利用根组件App.vue的特性:mouted 只会挂载一次
- 能否在人口文件main.js中使用?不能,this.$store只有组件有,在main.js中 this是undefined
合并参数:合并query和paramas参数(???)
- 在三家联动组件点击时传递了query参数,在搜索按钮点击时传递的是paramas参数,应该让两个动作传递的参数统一
home首页 listContainer|Floor组件业务:mock模拟假数据流程
- 安装依赖包mockjs
- 在src文件夹下创建一个文件夹,mock文件夹。
- 准备模拟的数据 把mock数据需要的图片放置于public文件夹中!*webpack打包时放入dist文件夹中 比如:listContainer中的轮播图的数据 [ {id:1,imgUrl:'xxxxxxxxx'}, {id:2,imgUrl:'xxxxxxxxx'}, {id:3,imgUrl:'xxxxxxxxx'}, ]
- 在mock文件夹中创建一个server.js文件 注意:在server.js文件当中引入json文件 banner.json||floor.json的数据没有进行暴露,但是可以在server模块中使用。 因为对于webpack当中一些模块:图片、json,不需要对外暴露,因为默认就是对外暴露。
- 通过mock模块模拟出数据
通过Mock.mock方法进行模拟数据 - 回到入口文件,引入serve.js mock需要的数据|相关mock代码页书写完毕,关于mock当中serve.js需要执行一次, 如果不执行,和没有书写一样的。
- 在API文件夹中创建mockRequest【axios实例:baseURL:'/mock'】 专门获取模拟数据用的axios实例。
获取mock 中的banner轮播图数据并显示在Listcontainer中
- 原来的/src/api/request.js是对服务器发Ajax请求,获取数据 现在是获取mock 的数据,因此新建/api/mockRequest.js
- 在/api/index.js进行mockRequest.js的统一管理
- Listcontainer组件挂载时,向store请求数据,则store下进行三连环操作
- ...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是建立轮播图结构的,遍历需要时间
- mouted中初始化:轮播图数据没有获取到
- updated中初始化: 能保证数据改变后(轮播图数据获取到)再初始化,如果组件有很多响应式(data),只要有一个属性值发生变化updated还会再次执行,再次初始化实例。
- 延迟器: 可以解决,但这种解决方案存在风险(无法确定用户请求到底需要多长时间),因此没办法确定 延迟器时间
- 单独的watch:可以保证轮播图数据已经获取,但是数据一获取就开始初始化轮播图实例,无法保证v-for遍历完成,出现结构
终极解决方案: watch+nextTick
获取floor组件mock数据
一般步骤
- 静态页面加载
- 仓库三连环操作存储数据(仓库中得有数据才能被请求到)
- 写请求仓库数据的api,触发仓库的action
- 组件获取数据进行展示
在哪里触发action?
bannnerList动作在Listcontainer组件中触发,但FloorList动作不能在Floor中触发,因为home组件中有两个Floor组件,是需要遍历到两个组件的数据
所以在Home组件中触发
1.注意数据的结构
2.:src
3.v-for循环比较复杂时可以直接用数组下标
Home首页轮播图拆分成一个全局组件
由于Home组件中有多个水品轮播图,(search中也有)所以可以复用; 很多组件公用的东西可以封装成全局组件
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现