vue实战项目-电商商城前台-(学习尚硅谷的)尚品汇

本文详细记录了使用Vue开发电商商城前端的过程,包括项目路由分析、Header和Footer组件、TypeNav三级联动、Home首页、Search搜索模块、登录注册、支付功能、个人中心组件的实现。讲解了路由跳转、组件交互、Vuex模块化开发、axios二次封装、API接口管理、数据模拟、状态管理、前端路由懒加载等多个关键点,旨在帮助开发者深入理解Vue项目实战。
摘要由CSDN通过智能技术生成

文章目录

最好使用视频上的账号密码,13700000000 密:111111

最新服务端接口地址:http://gmall-h5-api.atguigu.cn

因为该账号数据库存有地址信息 。

但是注意,因为很多同学同时操作,所以可能出现:购物车商品被其他同学增加或减少,订单不能重复提交等问题。此时稍等一会,或多试几次即可。

学习尚硅谷的商城前台尚品汇项目的笔记:

脚手架使用

1、创建项目

vue create 项目名称

2、脚手架默认目录:

  • node_modules:放置项目依赖的地方。
  • public:一般放置一些共用的静态资源,打包上线的时候,public文件夹里面资源原封不动打包到dist文件夹里面。
  • src:程序员源代码文件夹:
    • assets:经常放置一些静态资源(公用的图片(即很多组件都用此图)),assets文件夹里面资源webpack会进行打包为一个模块(js文件夹里面)
    • components:一般放置非路由组件(如共用的组件)
  • App.vue:唯一的根组件
  • main.js:入口文件【程序最先执行的文件】
  • babel.config.js:babel配置文件
  • package.json:项目描述、项目依赖、项目运行
  • README.md:项目说明文件

注意:

(放public中的图片,组件引用:images/1.png 组件的less中引用:url(/images/9.png);使用"/"作为根目录。 因为最后webpack打包,public中的图片原封不动打包到了images中,所以用绝对路径)

放assets中的图片,组件的html中引用:@/assets/2.png 组件的less中引用:~@/assets/7.png 因为assets文件夹里面资源webpack会进行打包为一个模块(js文件夹里面),所以用相对路径)

3、脚手架下载的项目稍微配置一下

  • 1)浏览器自动打开

    在 package.json 文件中

    ?        "scripts": {
    ?         "serve": "vue-cli-service serve --open",
    ?          "build": "vue-cli-service build",
    ?          "lint": "vue-cli-service lint"
    ?        },
    
  • 2)关闭 eslint 校验工具,以防写代码时没错也报错。

    在根目录创建 vue.config.js 文件:需要对外暴露

    module.exports = {
       lintOnSave: false,
    }
    
  • 3)src文件夹的别名的设置

    因为项目大的时候src(源代码文件夹):里面目录会很多,找文件不方便,设置src文件夹的别名的好处,找文件会方便一些。

    就不用用那么多…/…/了。直接@代替。

    (js用@代替src路径,css用~@代替src路径)

    创建 jsconfig.json 文件

    {
        "compilerOptions": {
            "baseUrl": "./",
            "paths": {
                "@/*": [
                    "src/*"
                ]
            }
        },
        "exclude": [
            "node_modules",
            "dist"
        ]
    }
    

一、项目路由分析

路由组件:

Home首页、Search搜索、login登录、Refister注册

非路由组件:

Header头部、Footer底部 (有Home首页、Search搜索组件。无login登录、Refister注册组件)

二、Header、Footer非路由组件完成

在开发项目的时候:
1: 书写静态页面(HTML + CSS)
2: 拆分组件
3: 获取服务器的数据动态展示
4: 完成相应的动态业务逻辑

需要用到less ,安装低版本,高版本容易出错。

yarn add less less-loader@5

(组件中,需要加 <style scoped lang="less">

1、使用非路由组件步骤:

  • 创建
  • 引入
  • 注册
  • 用标签使用

1、创建在 components下

2、引入到(需要的组件中)App.vue中,并注册、使用。

<template>
  <div id="app">
    <Header></Header>
    <Footer></Footer>
  </div>
</template>

<script>
import Header from './components/Header'
import Footer from './components/Footer'

export default {
  name: 'App',
  components: {
    Header,
    Footer
  }
}
</script>

2、使用路由组件步骤:

  • yarn add vue-router 3版本

  • 在src 创建 pages | views文件夹:放置路由组件。

  • 配置路由:

    在src 创建 router文件夹,建 index.js

    index.js:

    // 配置路由

    import Vue from ‘vue’;
    import VueRouter from ‘vue-router’;
    Vue.use(VueRouter);
    // 引入路由组件
    import Home from ‘@/pages/Home’
    import Search from ‘@/pages/Search’
    import Login from ‘@/pages/Login’
    import Register from ‘@/pages/Register’

    export default new VueRouter({
    routes: [
    {
    path: “/home”,
    component: Home
    },
    {
    path: “/Search”,
    component: Search
    },
    {
    path: “/Login”,
    component: Login
    },
    {
    path: “/Register”,
    component: Register
    },
    ]
    })

还要去main.js引入和注册路由

main.js

import Vue from 'vue'
import App from './App.vue'
// 引入路由
import router from '@/router';

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router
}).$mount('#app')

路由组件出口,路由组件展示

App.vue

<template>
  <div id="app">
    <Header></Header>
    <!-- 路由组件出口的地方、路由组件展示 -->
    <router-view></router-view>
    <Footer></Footer>
  </div>
</template>
1) 路由的跳转有两种形式:

注册完路由,不管路由路由组件、还是非路由组件身上都有 r o u t e 、 route、 routerouter属性。
$route:一般获取当前组件的路由信息 [路径、query、 params等等]
$router:一般进行编程式导航进行路由跳转 [push | replace]

1、声明式导航:router-link,可以进行路由的跳转

<router-link to="/login">登录</router-link>

2、编程式导航:利用组件实例的 $router.push | replace,可以进行路由跳转

	// 搜索按钮的回调函数,点击按钮跳转至search路由
    goSearch() {
      this.$router.push("/search");
    },

编程式导航:声明式导航能做的,编程式导航都能
但是编程式导航除了可以进行路由跳转,还可以做一些其他的业务逻辑。

2) 路由元信息:

将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过$route 的 meta属性 来实现,并且它可以在路由地址和导航守卫上都被访问到。

Footer组件的显示与隐藏

显示或隐藏组件: v-if | v-show

Footer组件 在Home、Search显示 在登录注册隐藏。

router中的index.js:

        {
            path: "/home",
            component: Home,
            meta: {show:true}
        },
        {
            path: "/search",
            component: Search,
            meta: {show:true}
        },
        {
            path: "/login",
            component: Login,
            meta: {show:false}
        },
        {
            path: "/register",
            component: Register,
            meta: {show:false}
        },
    ]


    <!-- meta.show在路由配置中定义 -->
    <Footer v-show="$route.meta.show"></Footer>
3) 路由传递参数

路由传参,参数有几种写法

params参数: 属于路径当中的一部分,需要注意,在配置路由的时候,需要占位

query参数: 不属于路径当中的一部分,类似于ajax中的queryString /homek=v&kv=,不需要占位

Header/index.vue

          <input
            type="text"
            id="autocomplete"
            class="input-error input-xxlarge"
            v-model="keyword"
          />   <!-- keyword数据绑定 -->


  data() {
    return {
      keyword: "",
    };
  },
  methods: {
    // 搜索按钮的回调函数,点击按钮跳转至search路由
    goSearch() {
      // 路由传递参数:
      // 第一种:字符串形式
      //   this.$router.push(
      //     "/search/" + this.keyword + "?k=" + this.keyword.toUpperCase()  // toUpperCase()转为大写字母
      // 第二种:模板字符串
      // this.$router.push(
      //   `/search/${this.keyword}?k=${this.keyword.toUpperCase()}`
      // );
      // 第三种:对象(常用)
      this.$router.push({
        name: "search",
        params: { keyword: this.keyword },
        query: { k: this.keyword.toUpperCase() },
      });
    },

search/index.vue

    <div>
        <h1>params参数---{
  { $route.params.keyword }}</h1>
        <h1>query参数---{
  { $route.query.k }}</h1>
    </div>

router/index.js

        {
        	// params参数在配置路由的时候,需要占位
            path: "/search/:keyword",
            component: Search,
            meta: {show:true},
            // 对象形式路由传递参数
            name: "search",
        },

路由传递参数面试题:

  • 路由传递参数(对象写法) path是否可以结合 params参数一起使用? 即:

    this.$router.push({ path: '/search', params: { keyword: this.keyword }, query: { k: this.keyword.toUpperCase() }, });

    答:报错,不能。

  • 如何指定 params参数 可传可不传? 即:

    this.$router.push({ name: "search", query: { k: this.keyword.toUpperCase() }, });

    答:配置路由时,path上加个 号,代表可传参数也可不传;若不加 ,则URL会出现问题。

    {
        path: "/search/:keyword?",
        component: Search,
        meta: {show:true},
        // 对象形式路由传递参数
        name: "search",
    },
    
  • params参数 可以传递也可以不传递,但是如果传递是空串,如何解决? 即:

      this.$router.push({name:"search",params:{keyWord:''},query:{k:this.keyWord}})
    

答:可以使用 undefined 来解决params参数可以传递也可不传递(空的字符串)

 this.$router.push({
    name: "search",
    params: { keyword: '' || undefined },
    query: { k: this.keyword.toUpperCase() },
  });
},
  • 路由组件能不能传递 props数据?

答:可以。三种写法:

 {
            path: "/search/:keyword",
            component: Search,
            meta: { show: true },
            // 对象形式路由传递参数
            name: "search",
            // 路由组件能不能传递 props数据?
            // 1、布尔值写法,但是这种方法只能传递params参数
            // props: true,
            // 2、对象写法:额外给路由组件传递一些props
            // props: { a: 1, b: 2 },
            // 函数写法(常用):可以params参数、query参数,通过props传递给路由组件
            props: ($route) => {
                return {keyword: $route.params.keyword, k: $route.query.k};
            }
        },


    <div>
        <h1>params参数---{
  { $route.params.keyword }}</h1>
        <h1>query参数---{
  { $route.query.k }}</h1>
        <h1>props数据---{
  { keyword }}</h1>
        <!-- <h1>props数据---{
  { a }}--{
  { b }}</h1> -->
    </div>
</template>

<script>

export default { 
    name: '',
    props: ['keyword', 'a', 'b'],
}
4) 重写push与repalce方法

编程式路由跳转 到当前路由(参数不变),多次执行跳转到当前路由,会抛出NavigationDuplicated的警告错误
– 路由跳转有两种形式:声明式导航、编程式导航
– 声明式导航 没有这类问题的,因为vue-router底层已经处理好了。“vue-router”: “^3.5.3” 最新的vue-router引入promise。
为什么 编程式导航 这时会有问题?

  this.$router.push({
    name: "search",
    params: { keyword: this.keyword },
    query: { k: this.keyword.toUpperCase() },
  },
  ()=>{},()=>{});

这种写法:治标不治本,将来在别的组件当中push | replace,编程式导航还是有类似错误。

this:当前组件实例( search)

this.$router:VueRouter的实例。当在入口文件注册路由的时候,给组件实例添加了$router | $route属性。

即: let $router = new VueRouter();

VueRouter是一个构造函数。

push:是VueRouter的一个方法

即: VueRouter.prototype.push = function() {}

$router 借用原型对象的方法:

$router.push(xxx)

router/index.js

// 保存原来的push函数 
let originPush = VueRouter.prototype.push;
// 保存原来的replace函数 
let originReplace = VueRouter.prototype.replace;
// 重写push函数,为解决相同路径跳转报错
// 第一个参数: 告诉原来push方法,你往哪里跳转(传递哪些参数)
// 第二个参数: 成功回调   第三个参数: 失败的回调
VueRouter.prototype.push = function(location, resolve, reject) {
    if(resolve && reject) {
        originPush.call(this, location, resolve, reject);
    } else {
        originPush.call(this, location, () => { }, () => { });
    } 
}
// 重写replace函数,为解决相同路径跳转报错
VueRouter.prototype.replace = function(location, resolve, reject) {
    if(resolve && reject) {
        originReplace.call(this, location, resolve, reject);
    } else {
        originReplace.call(this, location, () => { }, () => { });
    } 
}

三、TypeNav三级联动组件完成

因三级联动组件存在于Home、Search、Detail组件中,所以注册为全局组件。

main.js

// 三级联动组件--注册为全局组件(在各组件中使用就不需要引入了)
import TypeNav from '@/components/TypeNav'
// 第一个参数:全局组件的名字  第二个参数:哪一个组件
Vue.component(TypeNav.name, TypeNav)

components/TypeNav/index.vue

<template>
    <div>
        主页
        <TypeNav></TypeNav>
    </div>
</template>

<script>

</script>

<style lang="less" scoped>

</style>

四、Home首页拆分静态组件完成

在这里插入图片描述

Home/index.vue

<template>
    <div>
        <TypeNav/>
        <ListContainer/>
        <Recommend/>
        <Rank/>
        <Like/>
        <Floor/> 
        <Floor/> 
        <Brand/> 
    </div>
</template>

<script>
import ListContainer from '../Home/ListContainer';
import Recommend from '../Home/Recommend';
import Rank from '../Home/Rank';
import Like from '../Home/Like';
import Floor from '../Home/Floor';
import Brand from '../Home/Brand';
export default {
    name: '',
    components: {
        ListContainer,
        Recommend,
        Rank,
        Like,
        Floor,
        Brand,
    }
}
</script>

<style lang="less" scoped>

</style>

五、动态渲染三级联动部分

1、postman测试接口

填入服务器地址和请求地址,即
http://gmall-h5-api.atguigu.cn/api/product/getBaseCategoryList

在这里插入图片描述

200ok 说明成功。接口没问题。

2、axios 二次封装

为什么二次封装?

为了请求拦截器、响应拦截器。

请求拦截器:在发请求之前可以处理一些业务;

响应拦截器:当服务器数据返回以后,可以处理一些事情。

安装axios

yarn add axios

在项目中经常有 api文件夹,一般都是放 axios的

api/request.js

// 对于axios进行二次封装
import axios from "axios"

// 利用axios对象的方法create,去创建一个axios实例
// 这里的request 就是 axios,在这里配置一下
const request = axios.create({
    // 配置对象
    // 基础路径,发请求的时候,路径当中会默认有/api,不用自己写了
    baseURL: "/api",
    // 请求超时5s
    timeout: 5000,
})

// 请求拦截器:在发请求之前,请求拦截器可以检测到,在请求发出之前做一些事情;
requests.interceptors.request.use((config) => {
    // config:配置对象,其有一个重要属性:header请求头

})
// 响应拦截器:当服务器数据返回以后,可以处理一些事情。
requests.interceptors.response.use(((res) => {
    // 服务器响应成功的回调函数
    return res.data;
}, (error) => {
    // 服务器响应失败的回调函数
    return Promise.reject(new Error('faile'));
}))


// 对外暴露
export default requests;

3、API接口统一管理

若项目很小,可以在组件的生命周期函数中发请求

但项目大,组件多,若有更改,将麻烦。所以API接口统一管理。

什么是跨域?

同源就是指,域名、协议、端口均为相同。

跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。

vue.config.js

	// webpack中的代理跨域
    devServer: {
        proxy: {
            '/api': {
                // 服务器地址
                target: 'http://gmall-h5-api.atguigu.cn',
            },
        },
    },

api/index.js

import requests from "./request";

// 三级联动接口
export const reqCategoryList = () =>
    // 发请求:axios发请求返回结果是Promise对象
    requests({ url: "/product/getBaseCategoryList", method: "get" })

4、nprogress进度条的使用

安装: yarn add nprogress

在响应拦截器使用

api/request.js

// 引入进度条
import nprogress from 'nprogress'
// 引入进度条样式
import "nprogress/nprogress.css"


// 请求拦截器:
requests.interceptors.request.use((config) => {
    // config:配置对象,其有一个重要属性:header请求头
    // 进度条开始动
    nprogress.start();
    return config;

})
// 响应拦截器:
requests.interceptors.response.use((res) => {
    // 服务器响应成功的回调函数
    // 进度条结束
    nprogress.done();
    return res.data;
}, (err) => {
    // 服务器响应失败的回调函数
    return Promise.reject(new Error('faile'));
})

5、vuex 模块式开发

vuex 是官方提供的插件, 状态管理库,集中式管理项目中组件共用的数据 。

切记,并不是全部项目都需要 Vuex,如果项目很小,完全不需要Vuex,如果项目很大,组件很多、数据很多,数据维护很费劲,用Vuex

安装vuex yarn add vuex
在这里插入图片描述

main.js

// 引入仓库
import store from './store'

store/home/index.js

// home 模块的小仓库

// state:仓库存储数据的地方
const state = {}
// mutations:修改state的唯一手段
const mutations = {}
// actions:处理action,书写自己的业务逻辑、也可以处理异步
const actions = {}
// getters:计算属性,用于简化仓库数据,让组件获取仓库的数据更方便
const getters = {}

// 对外暴露
export default {
    state,
    mutations,
    actions,
    getters,
}

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

// 引入小仓库
import home from './home'
import search from './search'

export default new Vuex.Store({
    modules: {
        home,
        search,
    }
})

6、动态展示三级联动数据

api/index.js

import requests from "./request";

// 三级联动接口
export const reqCategoryList = () =>
    // 发请求:axios发请求返回结果是Promise对象
    requests({ url: "/product/getBaseCategoryList", method: "get" })

store/home/index.js

// home 模块的小仓库

import { reqCategoryList } from "@/api"

// state:仓库存储数据的地方
const state = {
    categoryList: [],
}
// mutations:修改state(数据)的唯一手段
const mutations = {
    CATEGORYLIST(state, categoryList) {
        state.categoryList = categoryList
    }
}
// actions:处理action,书写自己的业务逻辑、也可以处理异步
const actions = {
    // {commit}是因为其中要用到commit,原本是categoryList(context,value){}, 用commit时需要context.commit,{commit}可以省略context. 若不需要value则可不写。
    async categoryList({commit}) {
        // 向服务器发请求
        let result = await reqCategoryList();
        // console.log(result)
        // result.code == 200代表请求成功
        if (result.code == 200) {
            // 修改数据
            commit("CATEGORYLIST", result.data)
        }
    },
}

// 对外暴露
export default {
    state,
    mutations,
    actions,
    getters,
}

components/TypeNav/index.vue

<script>
import { mapState } from "vuex";
export default {
  name: "TypeNav",
  // 组件挂载完毕,可以向服务器发请求
  mounted() {
    // 通知Vuex发请求,获取数据,存储于仓库当中
    this.$store.dispatch("categoryList");
  },
  computed: {
    ...mapState({
      // 从仓库拿数据
      categoryList: (state) => state.home.categoryList,
    }),
  },
};
</script>


    <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.categroyId"
          >
            <dl class="fore">
              <dt>
                <a href="#">{
  { c2.categoryName }}</a>
              </dt>
              <dd>
                <em
                  v-for="(c3, index) in c2.categoryChild"
                  :key="c3.categroyId"
                >
                  <a href="#">{
  { c3.categoryName }}</a>
                </em>
              </dd>
            </dl>
          </div>
        </div>
      </div>
    </div>

7、动态一级菜单背景颜色

鼠标移出全部商品分类时,一级菜单背景消失

     <div @mouseleave="leaveIndex">
        <h2 class="all">全部商品分类</h2>

实现鼠标在当前标题,当前标题背景颜色设为蓝色,排他高亮显示

              <div
              class="item"
              v-for="(c1, index) in categoryList"
              :key="c1.categoryId"
              >
              <!-- 当鼠标进入当前的一级标题,则改变currentIndex为当前的index -->
			  <h3
                @mouseenter="changeIndex(index)"
                :class="{ cur: currentIndex == index }"
              > <!-- 若currentIndex==index ,则加类名cur,实现鼠标在当前标题,当前标题背景颜色设为蓝色,排他高亮显示 -->
                <a href="#">{
  { c1.categoryName }}</a>
              </h3>


  data() {
    return {
      // 存储用户移上哪一个一级分类
      currentIndex: -1,
    };
  },
  methods: {
    // 鼠标进入则修改响应式数据currentIndex属性
    changeIn
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值