十七、网上商城项目(5)

本章概要

  • 购物车
    • 购物车状态管理配置
    • 购物车组件
  • 结算页面
  • 用户管理
    • 用户状态管理配置
    • 用户注册组件
    • 用户登录组件

17.8 购物车

在一个电商网站中,购物车在很多页面都需要用到,因此非常适合放在 Vuex 的 store 中进行集中管理。在本项目中,采用模块化的方式管理应用中不同的状态。

17.8.1 购物车状态管理配置

在项目的 store 目录下新建 modules 文件夹,在该文件下新建 cart.js。如下:

store/modules/cart.js

const state = {
    items: []
}
//mutations
const mutations = {
    //添加商品到购物车中
    pushProductToCart(state, { id, imgUrl, title, price, quantity }) {
        if (!quantity)
            quantity = 1;
        state.items.push({ id, imgUrl, title, price, quantity });
    },

    //增加商品数量
    incrementItemQuantity(state, { id, quantity }) {
        let cartItem = state.items.find(item => item.id == id);
        cartItem.quantity += quantity;
    },
    //用于清空购物车
    setCartItems(state, { items }) {
        state.items = items
    },

    //删除购物车中的商品
    deleteCartItem(state, id) {
        let index = state.items.findIndex(item => item.id === id);
        if (index > -1)
            state.items.splice(index, 1);
    }
}

//getters
const getters = {
    //计算购物车中所有商品的总价
    cartTotalPrice: (state) => {
        return state.items.reduce((total, product) => {
            return total + product.price * product.quantity
        }, 0)
    },
    //计算购物车中单项商品的价格
    cartItemPrice: (state) => (id) => {
        if (state.items.length > 0) {
            const cartItem = state.items.find(item => item.id === id);
            if (cartItem) {
                return cartItem.price * cartItem.quantity;
            }
        }
    },
    //获取购物车中商品的数量
    itemsCount: (state) => {
        return state.items.length;
    }
}

//actions
const actions = {
    //增加任意数量的商品到购物车
    addProductToCart({ state, commit },
        { id, imgUrl, title, price, inventory, quantity }) {
        if (inventory > 0) {
            const cartItem = state.items.find(item => item.id == id);
            if (!cartItem) {
                commit('pushProductToCart', { id, imgUrl, title, price, quantity })
            } else {
                commit('incrementItemQuantity', { id, quantity })
            }
        }
    }
}

export default {
    namespaced: true,
    state,
    mutations,
    getters,
    actions
}

items 数组用于保存购物车中所有商品信息的状态属性。
接下来,编辑 store 目录下的 index.js ,导入 cart 模块。如下:

store/index.js

import { createStore } from 'vuex'
import cart from './modules/cart'
import createPersistedState from "vuex-persistedstate"

export default new Vuex.Store({
  modules: {
    cart
  },
  plugins: [createPersistedState()]
})

在刷新浏览器窗口时,store 中存储的状态信息会被重置,这样就会导致加入购物车中的商品信息丢失。所以一般会选择一种浏览器端持久存储方案解决这个问题,比较常见且简单的方案就是 localStorage ,保存在 store 中的状态信息也要同步加入 localStorage ,在刷新浏览器窗口前,或者用用户重新访问网站时,从 localStorage 中读取状态信息保存到 store 中。
在整个应用期间,需要考虑各种情况下 store 与 localStorage 数据同步的问题,这比较麻烦。为此,可以使用一个第三方的插件解决 store 与 localStorage 数据同步的问题,即 vuex-persistedstate 插件。
首先安装 vuex-persistedstate 插件,在 Visual Studio Code 的终端窗口中执行以下命令进行安装。

npm install vuex-persistedstate -S

vuex-persistedstate 插件的使用非常简单,只需要两句代码就可以实现 store 的持久化存储,这会将整个 store 的状态以 vuex 为键名存储到 localStorage 中。
如果只想持久化存储 store 中的部分状态信息,那么可以在调用 createPersistedState() 方法时传递一个选项对象,在该选项对象的 reducer() 函数中返回要存储的数据。例如:

plugins:[createPersistedState({
  reducer (data){
    return {
      // 设置只存储 cart 模块中的状态
      cart:data.cart,
      // 或者设置只存储 cart 模块中的 items 数据
      // products:data.cart.items
    }
  }
})]

reducer() 函数的 data 参数是完整的 state 对象。
如果想改变底层使用的存储机制,如使用 sessioniStorage,那么可以在选项对象中通过 storage 指定。代码如下:

plugins:[createPersistedState({
  reducer (data){
  	storage:window.sessionStorage,
    ...
  }
})]

配置好 Vuex 的状态管理后,就可以开始编写购物车组件了。

17.8.2 购物车组件

在 views 目录下新建 ShoppingCart。如下:

views/ShoppingCart.vue





ShoppingCart 组件提供了两种方式删除购物车中的某项商品:
(1)单击“删除”按钮,将直接删除购物车中的该商品
(2)用户单击数量下的减号按钮时,如果判断数量减一后为零,则删除该商品

17.9 结算页面

在购物车页面中单击“结算”按钮,则进入结算页面,结算页面再一次列出购物车中的所有商品,不同的是,在结算页面不能再对商品进行修改。
在 views 目录下新建 Checkout.vue。如下:

views/checkout.vue





在线支付涉及各个支付平台或银联的调用接口,所以本项目的购物车流程到这一步就结束了,当用户单击“付款”按钮时,只是简单地清空购物车,稍后提示用户“付款成功”。

17.10 用户管理

在实际场景中,当用户提交购物订单准备结算时,系统会判断用户是否已经登录,如果没有登录,会提示用户先进行登录,本节实现用户注册和用户登录组件。

17.10.1 用户状态管理配置

用户登录后的状态需要保存,不仅可以用于向用户显示欢迎信息,还可以用于对受保护的资源进行权限验证。同样,用户的状态存储也使用 Vuex 管理。
在 store/modules 目录下新建 user.js 。如下:

store/modules/user.js

const state = {
    user: null
}
// mutations
const mutations = {
    saveUser(state, { username, id }) {
        state.user = { username, id }
    },
    deleteUser(state) {
        state.user = null;
    }
}

export default {
    namespaced: true,
    state,
    mutations,
}

对于前端,存储用户名和用户 ID 已经足以,像用户中心等功能的实现,是需要重新向服务端去请求数据的。
编辑 store/index.js 文件,导入 user 模块,并在 modules 选项下进行注册。如下:

store/index.js

import { createStore } from 'vuex'

import cart from './modules/cart'
import user from './modules/user'
import createPersistedState from "vuex-persistedstate"

export default createStore({
  modules: {
    cart,
    user
  },
  plugins: [createPersistedState()]
})

17.10.2 用户注册组件

当用户单击 Header 组件中的 “注册”链接时,将跳转到用户注册页面。
在 components 目录下新建 UserRegister.vue 。如下:

components/UserRegister.vue





说明:
十七、网上商城项目(5)_第1张图片

十七、网上商城项目(5)_第2张图片

十七、网上商城项目(5)_第3张图片

红框处在这里实现了一个功能,当用户输入用户名时,实时去服务端检测该用户名是否已经存在,如果存在,则提示用户,这是通过 Vue 的监听器来实现的。
不过由于 v-model 指令内部实现机制的原因(对于文本输入框,默认绑定的是 input 事件),如果用户快速输入或快速用退格键删除用户名时,监听器将触发多次,由此导致频繁地向服务端发起请求。为了解决这个问题,可以利用 axios 的 cancel token 取消重复的请求。
使用 axios 发送请求时,可以传递一个配置对象,在配置对象中使用 cacelToken 选项,通过传递一个 executor() 函数到 CancelToken 的构造函数中创建 cancel token。
将 cancel() 函数保存为组件实例的方法,之后如果要取消请求,调用 this.cancel() 即可。cancel() 函数可以接收一个可选的消息字符串参数,用于给出取消请求的原因。同一个 cancel token 可以取消多个请求。
在发生错误时,可以在 catch() 方法中使用 this.axios.isCancel(error) 判断该错误是否是由取消请求而引发的。
当然,这里也可以通过修改 v-model 的监听事件为 change 解决快速输入和删除导致的重复请求问题,只需要给 v-model 指令添加 .lazy 修饰符即可。
用户名是否已注册的判断,请求的服务端数据接口如下:
http://111.229.37.167/api/user/{用户名}
返回的数据结构如下:

{
	"code": 200,
	"data": true  //如果要注册的用户名存在,则返回 false
}

用户注册请求的服务端数据接口如下:
http://111.229.37.167/api/user/register。
需要采用 Post() 方法向该接口发起请求,提交的数据是一个 JSON 格式的对象,该对象要包含 username、password 和 mobile 三个字段。
返回的数据结构如下:

{
  "code":200,
  "data":{
    "id":18,
    "username":"小鱼儿",
    "password":"1234",
    "mobile":"13222222222"
  }
}

实际开发时,服务端不把密码返回给前端,如果前端需要用到密码,则可以采用加密形式传输。
当用户注册成功后,将用户名和 ID 保存到 store 中,并跳转到根目录下,即网站的首页。然后 Header 组件会自动渲染出用户名,显示欢迎信息。

17.10.3 用户登录组件

当用户单击 Header 组件中的“登录”链接时,将跳转到用户登录页面。
在 components 目录下新建 UserLogin.vue 。如下:

components/UserLogin.vue





用户登录组件并不复杂,值得一提的就是在用户登录后需要跳转到进入登录页面前的路由,这会让用户体验更好,实现方式已经在 14.10.1 小节介绍过了,本项目也是利用 beforeEach() 注册的全局前置守卫保存用户登录前的路由路径,可以参看 17.11 节。
用户登录请求的数据接口如下:
http://111.229.37.167/api/user/login
同样是以 Post() 方法发起请求,提交的数据是一个 JSON 格式的对象,该对象要包含 username 和 password 两个字段。
返回的数据格式与用户注册返回的数据格式相同。

你可能感兴趣的:(Vue.js,3.0,从入门到实战,购物车,结算页面,用户管理)