商城项目整体构建

文章详细阐述了一个基于Vue的项目结构,包括路由配置、Vuex状态管理、组件通信以及使用Mock.js模拟数据。重点讨论了购物车功能的实现,如商品添加、减少、删除,以及状态管理。此外,还涉及到了Vue的生命周期、接口文档和API管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、

1.项目结构分配、模块的定义、组件封装调用(先定义、然后导入 注册 使用)、路由拦截、cookie理解、token权限、路由权限管理、动态路由、打包部署、vuex状态管理、接口文档

对于跳转,要先设置路由确保组件可以正常使用、路由可以正常跳转,再充实页面

对于左边导航与上边面包屑的联动,可以通过this.$route.matched获取该路由的所有信息,然后在每一个路由中写一个meta对应路由的内容,在面包屑那块通过遍历当前路由信息展示meta的值

对于导航栏的多级菜单,可以通过递归进行展示

父子组件传值的时候,子组件中通过props接收父组件传过来的值

二、流程

1.脚手架创建项目,并且 关闭eslint校验以防写代码时没错也报错

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

2.在开发项目的时候:

非路由组件:

  • 书写静态页面(HTML + CSS)
  • 拆分组件
  • 获取服务器的数据动态展示
  • 完成相应的动态业务逻辑

路由组件

  • 创建组件:Vue.component(tagName, options)                   

                         var 组件内容 = Vue.extend({template: '<div>自定义全局组件,使用Vue.extend</div>'})Vue.component("组件名称",组件内容)

  • 在router中创建并配置具体路由
  • 在main.js中引入进行全局注册路由
  • 在app.vue中:<!-- 路由组件出口的地方、路由组件展示 --> <router-view></router-view>
  • 在需要的地方引入标签

3.mockjs模拟数据 

使用Mock.js插件,生成随机数据,拦截 Ajax 请求。

  • 前后端分离:让前端攻城师独立于后端进行开发。
  • 增加单元测试的真实性:通过随机数据,模拟各种场景。
  • 开发无侵入:不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据。
  • 用法简单:符合直觉的接口。
  • 数据类型丰富:支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等。
  • 方便扩展:支持支持扩展更多数据类型,支持自定义函数和正则。

4.购物车的管理

1.逻辑:点击加入购物车,将详情页的数据加入购物车中;

此时需要对state中的cartList进行修改,点击加入购物车就会执行store文件中actions对象里定义的加入购物车方法,actions对象中对加入的商品进行判断,若之前商品已存在,则数量加1,若商品不存在,则将商品添加进cartList。其返回一个promise对象。

2.组件:在购物车模块里可以查看详细信息 更新数量,全选反全选  取消加购 去结款

  • 将加入购物车中的商品对象放到cartList中了,商品对象中包括需要在购物车中展示的详细信息。首先将通过store中的getters属性接收vuex中的数据,通过v-for来对cartList中的商品对象进行遍历,同时展示单个商品信息的组件通过props来接收父组件传来的单个商品对象;在计算属性computed中接收vuex中挂载的数据。
  • 通过数组的filter方法找出选中的商品 ,然后通过数组的reduce方法对选中商品的价值总额进行计算;
  • 全选的逻辑:若部分商品或者全部商品未被选中,则利用forEach使cartList中的每个商品为选中状态;若全部选中,则使cartList中的每个商品为未选中状态

3.数据交互:通过vuex状态管理机制来实现购物车的数据交互,创建store文件并挂载在vue实例上,在store文件中定义一个可以挂载数据的state,在其中定义一个数组cartList来存放商品信息,其他组件就可以获取并使用这个数据了。

store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
 
export default new Vuex.Store({
    state: {
        carList: [] //购物车的商品
    },
    mutations: {
        // 加
        addCar(state, params) {
            let CarCon = state.carList;
            // 判断如果购物车是否有这个商品,有就只增加数量,否则就添加一个
            // some 只要有一个isHas为true,就为true
            let isHas = CarCon.some((item) => {
                if (params.id == item.id) {
                    item.num++;
                    return true;
                } else {
                    return false;
                }
            })
 
            if (!isHas) {
                let obj = {
                    "id": params.id,
                    "title": params.title,
                    "price": params.price,
                    "num": 1,
                }
                this.state.carList.push(obj)
            }
        },
        // 减
        reducedCar(state,params){
            let len=state.carList.length;
            for(var i=0;i<len;i++){
                if(state.carList[i].id==params.id){
                    state.carList[i].num--
                    if(state.carList[i].num==0){
                        state.carList.splice(i,1);
                        break;
                    }
                }
            }
        },
        //移出
        deleteCar(state,params){
            let len=state.carList.length;
            for(var i=0;i<len;i++){
                if(state.carList[i].id==params.id){
                    state.carList.splice(i,1);
                    break;
                }
            }
        },
 
         // 初始化购物车,有可能用户一登录直接进入购物车
        // initCar(state, car) {
        //     state.carList = car
        // },
 
    },
    actions: {
        // 加
        addCar({ commit }, params) {
            // console.log(params) //点击添加传过来的参数
            // 使用setTimeout模拟异步获取购物车的数据
            setTimeout(function () {
                let result = 'ok'
                if (result == 'ok') {
                    // 提交给mutations
                    commit("addCar", params)
                }
            }, 100)
        },
        // 减
        reducedCar({ commit }, params) {
            // console.log(params) //点击添加传过来的参数
            // 使用setTimeout模拟异步获取购物车的数据
            setTimeout(function () {
                let result = 'ok'
                if (result == 'ok') {
                    // 提交给mutations
                    commit("reducedCar", params)
                }
            }, 100)
        },
        // 移出
        deleteCar({ commit }, params) {
            // console.log(params) //点击添加传过来的参数
            // 使用setTimeout模拟异步获取购物车的数据
            setTimeout(function () {
                let result = 'ok'
                if (result == 'ok') {
                    // 提交给mutations
                    commit("deleteCar", params)
                }
            }, 100)
        }
        // initCar({ commit }) {
        //     setTimeout(function () {
        //         let result = 'ok'
        //         if (result == 'ok') {
        //             // 提交给mutations
        //             commit("initCar", [{
        //                 "id": 20193698,
        //                 "title": '我是购物车原来的',
        //                 "price": 30,
        //                 "num": 100,
        //             }])
        //         }
        //     }, 100)
        // }
    },
    getters: {
        //返回购物车的总价
        totalPrice(state) {
            let Carlen = state.carList;
            let money = 0;
            if (Carlen.length != 0) {
                Carlen.forEach((item) => {
                    money += item.price * item.num
                })
                return money;
            } else {
                return 0;
            }
        },
        //返回购物车的总数
        carCount(state) {
            return state.carList.length
        }
    },
})


list.vue

<template>
  <!-- 商品列表 -->
  <div id="listBox">
    <!--  -->
    <router-link :to="{path:'/car'}" style="line-height:50px">跳转到购物车</router-link>
    <el-table :data="tableData" border style="width: 100%">
      <el-table-column fixed prop="id" align="center" label="商品id"></el-table-column>
      <el-table-column prop="title" align="center" label="商品标题"></el-table-column>
      <el-table-column prop="price" align="center" label="商品价格"></el-table-column>
      <el-table-column label="操作" align="center">
        <template slot-scope="scope">
          <el-button @click="addCar(scope.row)" type="text" size="small">加入购物车</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>
 
<script>
export default {
  name: "listBox",
  data() {
    return {
      tableData: [] //商品列表
    };
  },
  methods: {
    // 初始化商品列表
    initTable(){
      this.$gAjax(`../static/shopList.json`)
        .then(res => {
          console.log(res)
          this.tableData=res;
        })["catch"](() => {});
    },
    // 加入购物车
    addCar(row){
      // console.log(row)
      // 提交给store里面actions 由于加入购物车的数据要同步到后台
      this.$store.dispatch('addCar',row)
    }
    
  },
  mounted () {
    this.initTable()
  }
};
</script>
<style>
#listBox {
  width: 900px;
  margin: 0 auto;
}
</style




cart.vue
<template>
  <!-- 购物车 -->
  <div id="carBox">
    <!-- 商品总数 -->
    <h2 style="line-height:50px;font-size:16px;font-weight:bold">合计:总共{{count}}个商品,总价{{totalPrice}}元</h2>
    <p v-if="count==0">空空如也!·······</p>
    <div v-else>
      <el-table :data="carData" border style="width: 100%">
        <el-table-column fixed prop="id" align="center" label="商品id"></el-table-column>
        <el-table-column prop="title" align="center" label="商品标题"></el-table-column>
        <el-table-column prop="price" align="center" label="商品价格"></el-table-column>
        <el-table-column label="操作" align="center">
          <template slot-scope="scope">
            <el-button @click="reduceFun(scope.row)" type="text" size="small">-</el-button>
            <span >{{scope.row.num}}</span>
            <el-button @click="addCar(scope.row)" type="text" size="small">+</el-button>
 
            <el-button @click="deleteFun(scope.row)" type="text" size="small">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>
 
<script>
export default {
  name: "carBox",
  data() {
    return {};
  },
  computed: {
    //购物车列表
    carData() {
      return this.$store.state.carList;
    },
    //商品总数
    count() {
      return this.$store.getters.carCount;
    },
    //商品总价
    totalPrice() {
      return this.$store.getters.totalPrice;
    }
  },
  methods: {
    // 增加数量
    addCar(row){
       this.$store.dispatch('addCar',row)
    },
    // 减数量
    reduceFun(row){
       this.$store.dispatch('reducedCar',row)
    },
    // 删除
    deleteFun(row){
        this.$store.dispatch('deleteCar',row)
    }
 
    // 用户首次登录请求购物车的数据
    // initCar(){
    //   this.$store.dispatch('initCar')
    // }
  },
  created () {
    // this.initCar();
  },
  mounted() {}
};
</script>
 
<style>
#carBox {
  width: 900px;
  margin: 0 auto;
}
</style>

三、知识点记录

路由跳转的两种方式:

  • 声明式导航:router-link,可以进行路由的跳转<router-link to="/login">登录</router-link>
  • 编程式导航:利用组件实例的 $router.push | replace,可以进行路由跳转

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

路由元信息:

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

路由参数传递

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

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

路由传递参数(对象写法) 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会出现问题。
params参数 可以传递也可以不传递,但是如果传递是空串,如何解决? 

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

路由组件能不能传递 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};
            }
        },

为什么进行axios 二次封装

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

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

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

// 对于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;

API接口统一管理

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

但项目大,组件多,若有更改,将麻烦。所以API接口统一管理。比如跨域的代理可以统一管理

nprogress进度条的使用

在响应拦截器使用

// 请求拦截器:
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'));
})

vuex 模块式开发

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

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

图片懒加载

// 引入图片懒加载插件
import VueLazeload from 'vue-lazyload';
// 引入懒加载默认图片(即真实图片没加载好之前,加载时显示的图片)
import tp from '@/assets/images/1.png';
// 注册插件
Vue.use(VueLazeload, {
  // 懒加载默认图片,(即真实图片没加载好之前,加载时显示的图片)
  loading: tp,
})




<!-- v-lazy自定义指令图片懒加载 -->
<img v-lazy="good.defaultImg" />

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值