前端“尚品汇”大型购物商城项目(前台页面)系列1

这篇博客是在看完尚硅谷的"前端尚品汇大型购物商城项目"后做的笔记,以此来记录在这个过程中学到的知识。这个系列博客还有后台项目笔记,欢迎大家去看我这个系列的博客哦~
视频链接
后台项目笔记链接

1、前台项目涉及到的页面

  • 首页
  • 商品分类页面
  • 商品详情页面
  • 购物车添加页面
  • 购物车页面展示
  • 支付页面
  • 支付成功页面
  • 我的页面
  • 注册页面
  • 登录页面

下面的记录顺序,我打算按照从买商品的流程顺序去写。而是以功能为主,记录其中重要的功能点,以及实现中涉及到了什么技术,中间会穿插代码以及图的方式进行展示。

2、vue中的文件夹的介绍

  1. node_modules文件夹:项目依赖文件夹
  2. public文件夹:一般放置一些静态资源,需要注意的是,放在public文件夹中的静态资源,webpack进行打包的时候,会原封不动的打包到dist文件夹中
  3. src文件夹(程序的源代码)
    assets文件夹:一般也是放置静态资源(多个组件公用的静态资源),在打包webpack的时候,webpack会被当做是一个模块,打包到JS文件里面。
    components:用于放置非路由组件(全局组件)
    App.vue:唯一的根组件,Vue当中的组件。
    main.js:程序入口文件,也是整个程序当中最先执行 的文件
  4. babel.config.js:配置文件(babel相关)
  5. package.json:项目叫了什么,项目的版本号是什么
  6. package-lock.json:缓存文件。
  7. 自动打开浏览器:在serve后面加上open
    package.json文件
	  "scripts": {
	    "serve": "vue-cli-service serve --open",
	    "build": "vue-cli-service build",
	    "lint": "vue-cli-service lint"
	  },

3、如何修改vue模板

对应的快捷键是:修改vue.json文件,使得在vue页面,输入vue+回车键,就可以生成vue模板

  1. 用快捷键Ctrl+Shift+P唤出控制台
  2. 入“snippets”并选择
  3. 输入vue,选择vue.json
  4. 输入模板

4、对axios二次封装

1、创建axios实例,配置基础路径,当发请求的时候,路径中出现api
2、请求拦截器:在发请求之前,请求拦截器可以检测到。方便请求之前进行一些操作。①在请求头中添加字段;②将携带的token带给服务器。
3、响应拦截器:服务器在相应数据回来之后,相应拦截器可以检测到。①将返回的data字段传递过去。

const requests = axios.create({
    // 配置对象
    // 基础路径,发请求的时候,路径当中会出现api
    baseURL: "/api",
    // 请求超时的时间
    timeout: 5000,
});
// 请求拦截器:在发请求之前,请求拦截器可以检测到,可以再请求发出之前做一些事情
requests.interceptors.request.use((config) => {
    // config:配置对象,对象里面有一个属性很重要,header请求头
    if (store.state.detail.uuid_token) {
        // 请求头添加一个字段(userTempId)
        config.headers.userTempId = store.state.detail.uuid_token
    }
    // 需要携带token带给服务器
    if (store.state.user.token) {
        config.headers.token = store.state.user.token;
    }
    // nprogress.start();
    return config
})
// 相应拦截器
requests.interceptors.response.use((res) => {
    // 成功的回调函数:服务器相应数据回来以后,响应拦截器可以检测到,可以做一些事情
    return res.data;
}, (error) => {
    // 服务器响应失败的回调函数
    return Promise.reject(new Error('fail'))
})
// 对外暴露
export default requests;

另外还需要在vue.comfig.js中配置代理,解决跨域问题:

 devServer: {
    proxy: {
      "/api": {
        target: "http://gmall-h5-api.atguigu.cn",
        changeOrigin: true, //开启代理:在本地会创建一个虚拟服务端,然后发送请求的数据,并同时接收请求
        ws: true,
        // pathRewrite: { "^/api": "" },
      }
    }
  }

接下来就可以向服务器请求数据啦~

5、app.vue页面的配置

在这个页面中放置了头部组件,中间路由组件,尾部组件。但是尾部组件在登录注册页面中不显示,因此在routes.js中配置路由信息的时候,为每个路由添加了meta对象,规定注册登录页面meta: { show: true }。

<Header></Header>
<!-- 路由组件出口的地方 -->
<router-view></router-view>
<Footer v-show="$route.meta.show"></Footer>

6、首页选择商品类别

首页涉及到了三级联动功能,这个也是首页最复杂的一个功能。

在这里插入图片描述

  1. 三级联动数据获取(请求数据是发生在了app.vue页面上),写在了监视属性里面。

  2. 利用v-for语句循环遍历,使其能够在页面上进行展示。

  3. 添加自定义属性,使得发生点击事件的时候,可以获取到点击的属性值。例如:

    :data-categoryName="c1.categoryName"
    :data-category1Id="c1.categoryId"
    
  4. 添加点击事件。在点击函数中,需要要携带参数进行跳转。携带的参数有分类名以及ID。

  5. 参数获取方式:根据event.target来获得点击对象。然后解构出来属性。因为是三级联动,因此会有三个属性。那么就要分别判断哪个有值了。将分类名称以及id放入到query中。

  6. 判断路由在跳转的饿时候是否携带有params参数,这个参数是在搜索那块加进去的,因为搜索的时候需要将搜索的结果呈现到地址栏中,因此设置的是params参数来去携带。

  7. 参数整理完毕就可以跳转了。this.$router.push(location);

    注:为了防止用户鼠标移动每个分类移动的过快,从而造成页面频繁重绘,这里对其进行优化。引入了节流功能。
    
   // 频繁进入这个三级联动框内,用到了节流操作
    changeIndex: throttle(function (index) {
      // 鼠标进入,修改响应式数据currentIndex数据
      this.currentIndex = index;
    }, 100),

7、特定分类的商品属性选择

经过三级列表选择,便进入到了这个页面。它可以选择这个类别下的属性值,然后根据这些展现出来的商品,点击查看这个产品的细节情况。
比如说我想看手机,那我就要在三级分类那边点击手机这个选项,然后呢,我选择品牌是小米的,运行内存是4G的手机有哪些,页面下方就进行展现,我只需要点击我喜欢的那款手机,就可以看到这款手机有价格属性之类的信息啦。

在这里插入图片描述
那么如何选择这些属性,又是如何收集的呢?

  • 页面数据的获取:他是在页面加载完成之前,在beforemount钩子里面,获取到路由中带的参数。在mounted里面获取请求数据。调用的是store中的getSearchList方法。然后在计算属性里面调用mapGetters中的goodsList方法,获得商品列表。接下来就是点击属性,更新下面的商品列表啦。
  • 首先将属性分为类别属性、关键字属性、品牌属性、平台售卖属性。分类属性存在路由query里面,关键字属性存在于路由的params里面,品牌属性需要在品牌选择栏里面进行选择,平台售卖属性同样需要在属性选择栏中进行选择。先前品牌属性还有平台售卖尚需经还没有收集到。
  • 将属性选择栏封装成组件。并定义了两个自定义函数。用于子向父传递数据,收集到平台售卖属性以及品牌属性。
    <SearchSelector @trademarkInfo="trademarkInfo" @attrInfo="attrInfo" />
    
  • 在<SearchSelector>这个组件中呢。他的数据来源是从store仓库里面得到的数据。将其放入计算属性里面。
  • 定义两个点击事件,点击品牌属性的时候触发子组件身上的自定义方法trademarkInfo,点击平台售卖属性触发子组件身上的自定义方法attrInfo
  • 子组件中定义的两个自定义方法呢,是写在了父组件里面的,方法的中可以获得子组件传递过来的数据。然后数据就会收集在了searchParams里面。并再次向store中获取数据。在页面中进行更新。
    // 自定义事件的回调
        trademarkInfo(trademark) {
          // 整理参数
          this.searchParams.trademark = `${trademark.tmId}:${trademark.tmName}`;
          this.getData();
        },
        // 收集平台属性地方回调函数(自定义事件)
        attrInfo(attr, attrValue) {
          let props = `${attr.attrId}:${attrValue}:${attr.attrName}`;
          if (this.searchParams.props.indexOf(attrValue) == -1) {
            this.searchParams.props.push(props);
          }
          this.getData();
        },
    
  • 下面四个属性收集完成,而且商品列表页已经显示出来了,接下来就是点击商品列表进行跳转了, 采用的声明式路由,并且将商品id一并也带到detail页面中去。
    <router-link :to="`/detail/${goods.id}`">
      <img :src="goods.defaultImg" />
    </router-link>
    

小总结:

1、在这个页面当中,他的页面中所呈现的数据都是从store仓库中获取到的,

// 获取search模块数据
    async getSearchList({ commit }, params = {}) {
        // 当前这个reqGetSearchInfo
        const result = await reqGetSearchInfo(params)
        // params形参:是当用户派发action的时候,第二个参数传递过来的,至少是一个空对象
        if (result.code == 200) {
            commit("SESRCHLIST", result.data);
        }
    }

2、然后通过getters属性简化了仓库中的数据,其中的goodsList属性给了父组件,trademarkList和attrsList给了子组件(SearchSelector)。

const getters = {
    // 当前形参state,当前仓库中的state,并非大仓库中的state
    goodsList(state) {
        // state.searchList.goodsList如果服务器数据回来了,是一个数组
        // 但是如果网络不给力,返回的是undefined
        return state.searchList.goodsList || [];
    },
    trademarkList(state) {
        return state.searchList.trademarkList || [];
    },
    attrsList(state) {
        return state.searchList.attrsList || [];
    }
}

3、所有数据的收集都是在父组件中完成的,而每当修改属性,最后都要进行一次获取数据的操作。有路由参数的变更(分类属性以及关键字),品牌属性的勾选,平台销售属性的勾选。当然了有选择也会有删除操作。还有价格总结那块点击之后也会更新背景颜色以及图标。还有父子组件通信通过的是自定义方法来实现的。

4、综合/价格的排序选择,也是通过从候选获取已经排好了的数据然后再前台页面进行展示。

8、选择哪个款式的商品,然后加入购物车

经过搜索页面,挑选商品之后,便进入到了这个页面。我们可以在这个页面当中选择商品的款式,他的颜色呀,版本号呀,以及想要购买的数量。点击加入购物车之后,便可以跳转到购物车页面啦。

在这里插入图片描述

  • 首先是数据的获取。这个页面需要得到的数据是categoryView(产品分类)、skuInfo(产品信息)、spuSaleAttrList(产品售卖属性)。产品分类属性主要是在页面的头部区域进行展示的,说明中这个产品属于哪一个三级分类。接下来说一下这个产品信息这块是要如何展示的。
  • 产品信息这块有一个放大镜还有轮播图。分别封装了两个子组件:<zoom>以及<ImageList>,
     <!-- 左侧放大镜区域 -->
      <div class="previewWrap">
        <!--放大镜效果-->
        <Zoom :skuImageList="skuImageList" />
        <!-- 小图列表 -->
        <ImageList :skuImageList="skuImageList" />
      </div>
    
  • <zoom>组件中自定义属性skuImageList,将图片列表传递过去。因为底下的轮播图的改变会影响到上面图片的展示,因此涉及到了子组件之间的通信。这里用到的是$bus,来传递数据。放大镜具体是怎么实现的,会在后面的博客中介绍,这里就不在过多介绍细节,而是介绍了图片是如何获得的。下面是轮播图功能。
    zoom订阅了这个getIndex属性
      mounted() {
        // 全局事件总线,获取兄弟组件传递过来的索引值
        this.$bus.$on("getIndex", (index) => {
          this.currentIndex = index;
        });
      },
    
    ImageList发布getIndex属性
     changeCurrentIndex(index) {
          this.currentIndex = index;
          // 点击的时候,也要通知兄弟组件,当前的索引值
          this.$bus.$emit("getIndex", index);
        },
    
  • 轮播图这个功能用到了Swiper这个插件。数据是通过自定义属性skuImageList获得的。但是这里涉及到了一个问题。就是数据在页面加载完毕之后才收集到,虽然将skuImageList写在了监视属性里面,可以检测到他的更新变化,页面这个时候监听到watch中的数据发生了变化,v-for就会遍历数组,并在页面上进行展示。但是如果这个时候直接使用Swiper插件来完成轮播的话,会出问题,因为这个时候可能v-for还没有遍历结构遍历完成呢,所以就需要用到了nextTick啦。
  • 他可以更新DOM。nextTick什么时候用到:需要将所有数据加载完成后再更新DOM,则用到了nextTick。轮播图用js代码的实现步骤,在后续的博客中会详细介绍。到这里为止,放大镜以及轮播图就介绍完了。接下来就是选择商品属性,然后进行收集,加入购物车啦。
  • 在商品属性选择中。对spuSaleAttrList,for循环,然后再页面上进行展示。并为每一项添加点击事件,修改isChecked属性。
  • 点击添加购物车。将商品id还有商品的数量传递过到store中的UpdateShopCart方法里面,然后再发起请求。将数据传递回服务器。可以看到这里是把商品的详情存储到了sessionStorage之中。方便后面添加购物车成功页面得到这个skuInfo然后进行展示。
        async addShopCar() {
          try {
            let result = await this.$store.dispatch("UpdateShopCart", {
              skuId: this.$route.params.skuId,
              skuNum: this.skuNum,
            });
            // 本地存储,会话存储,这俩一般都只能存字符串
            sessionStorage.setItem("SKUINFO", JSON.stringify(this.skuInfo));
            this.$router.push({
              name: "addcartsuccess",
              query: {
                skuNum: this.skuNum,
              },
            });
          } catch (error) {
            console.log(error);
          }
        },
    

注意:

轮播图需要注意的是,他是将图片渲染写在了watch+$nextTick()里面。为什么要这样写呢?
首先轮播图数据是通过props获取得到的。也就是从父组件那传递过来的,父组件则是从服务器拿过来的,请求的发送是异步的,但是页面的渲染确是很快的。
而Swiper的使用前提是:
①:必须在数据已经存在;
②:且页面也已经渲染完成之后,才生效。不然就会报错。
那么就必须保证props拿到的数据必须要有,有人可能觉得那用watch监听一下不就行啦。props中的数据是单向的,即数据到页面,当数据变化,页面也会随之发生变化,然后watch中就会检测到数据的变化,此时v-for正在遍历数组,但是如果说他还没有遍历完成,就不满足swiper使用的两个前提中第二个前提,所以需要使用$nextTick();

watch: {
    // 监听数据:可以保证数据是可以的,但是不能保证v-for遍历结构是否完成
    skuImageList(newValue, oldValue) {
      // 它可以获取更新后的DOM
      this.$nextTick(() => {
        var mySwiper = new Swiper(".swiper-container", {
          // 如果需要前进后退按钮
          navigation: {
            nextEl: ".swiper-button-next",
            prevEl: ".swiper-button-prev",
          },
          // 显示几个图片设置
          slidesPerView: 3,
        });
      });
    },
  },

9、点击加入购物车成功之后,就会进入添加成功的页面

在这页面你可以反悔,我想再看看别的商品,就可以返回到之前的那个页面。如果说我不想买了,就可以进入到购物车页面。

在这里插入图片描述
跳转,用到的是声明式路由:

 <div class="right-gocart">
    <router-link class="sui-btn btn-xlarge" :to="`/detail/${skuInfo.id}`"
      >查看商品详情</router-link
    >
    <router-link to="/shopcart">查看商品详情</router-link>
  </div>

紧接着上面的那个问题来讲,这里从sessionStorage中 取出了之前存放的数据。这个是属于组件与组件之间传递数据了。也可以用$bus来完成。也可以放在路由中的query属性里面。

  • 3
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值