智慧商城项目总结

智慧商城

项目收获

  1. 完整电商购物的业务流
  2. 组件库vant-ui的学习与使用
  3. 移动端的vw适配(基于postcss插件实现)
  4. request请求方法的封装
  5. storage存储模块的封装
  6. api请求模块的封装
  7. 请求响应拦截器
  8. 嵌套路由配置
  9. 路由导航守卫
  10. 路由跳转传参
  11. vuex分模块管理数据
  12. 项目的打包优化

项目前期准备工作

  1. 调整初始化目录在这里插入图片描述

引入vant组件库

  1. vant的全部导入 在这里插入图片描述

  2. vant的按需导入在这里插入图片描述

  3. 其他的vue组件库在这里插入图片描述

使用postcss插件实现的vw适配

在这里插入图片描述

路由设计配置

  1. 确定一级路由和二级路由,例如展示一个完整的页面的就需要配置一级路由
  • 根据路由设置,在views文件夹中创建对应的组件
  1. 通过阅读vant组件库文档,实现底部导航tabbar在这里插入图片描述
  2. 配置二级路由在这里插入图片描述
  routes: [
    { path: '/', component: layout, redirect: '/home' },
    {
      path: '/layout',
      component: layout,
      children: [
        { path: '/cart', component: cartPage },
        { path: '/category', component: catecategoryPageory },
        { path: '/home', component: homePage },
        { path: '/user', component: userPage }
      ]
    },
    { path: '/login', component: login },
    { path: '/search', component: search },
    { path: '/searchList', component: searchList },
    { path: '/prodetail/:id', component: goodsDetail },
    { path: '/pay', component: pay },
    { path: '/myOrder', component: myOrder },
    { path: '/address', component: address },
    { path: '/update', component: update }

  ]

登录模块

登录静态页面

在这里插入图片描述

  • 顶部使用vant组件库中的NbvBar导航条
  • <van-nav-bar title="会员登录" left-arrow @click-left="$router.go(-1)" />

request模块 - 基于axios封装

  1. 使用导入axios包,通过axios.create()函数创建实例,并配置基地址baseUrl和请求时间timeout,优点如下:

    • 全局配置管理:通过创建axios实例,可以在一个地方集中管理所有的请求配置,例如基本URL、默认头部信息、超时设置等。这样可以确保整个项目中的请求遵循统一的规范,提高了代码的可维护性。
    • 拦截器管理:axios实例可以使用拦截器来统一处理请求和响应,例如在请求发送前做一些处理(如添加token),或者在响应返回后对数据进行统一处理(如错误处理或消息提示)。通过创建实例,可以更灵活地管理拦截器,根据需要对不同的实例设置不同的拦截逻辑
    • 实现多个后端接口:在大型项目中,可能需要同时访问多个后端接口,而这些接口可能具有不同的基本URL或者需要不同的认证信息。通过创建多个axios实例,可以轻松地管理不同的接口请求,而不会产生混乱
    • 避免跨域问题:在实际开发中,可能会遇到跨域请求的问题。通过创建axios实例,可以在实例中统一配置跨域请求所需的相关信息,从而避免在每次请求中都需要手动处理跨域。
  2. 创建请求响应拦截器在这里插入图片描述

图形验证码功能

在这里插入图片描述

   // 获取图形验证码
    async getPicImage () {
      const { data: { base64, key } } = await getPicImage()
      // 获取到的图片地址
      this.PicImage = base64
      // 图片的唯一标识,将来发送到服务器验证
      this.PicKey = key
    },

api接口模块

在这里插入图片描述

Toast轻提示

在这里插入图片描述

短信验证码倒计时

  1. 步骤分析在这里插入图片描述
  2. 代码实现
在这里插入代码片 // 实现倒计时效果
    async countdown () {
      // 前端校验处理函数
      if (!this.validFn()) {
        return
      }
      // 当timer为空(此时没有开启定时器)和两个second(一个second控制时间,一个演示倒计时效果)相等时开启
      if (!this.timer && this.second === this.totalSceond) {
        // 发送验证码请求
        const res = await getMcode(this.PicCode, this.PicKey, this.mobile)
        console.log(res.data)

        // 在响应拦截器里统一设置错误处理
        // 以下是成功的情况
        this.$toast('获取短信验证码成功')
        this.timer = setInterval(() => {
          this.second--

          if (this.second <= 0) {
            clearInterval(this.timer)
            this.second = this.totalSceond
            this.timer = null
          }
        }, 1000)
      }
    },
 // 前端校验
    validFn () {
		// 判断手机号的正则表达式
      if (!(/^1[3-9]\d{9}$/.test(this.mobile))) {
        this.$toast('请输入正确手机号')
        return false
      }

      if (!/^\w{4}$/.test(this.PicCode)) {
        this.$toast('请输入正确验证码')
        return false
      }
      return true
    }
  },
  // 页面关闭,清除定时器ID
   destroyed () {
    clearInterval(this.timer)
  }

实现登录功能

  1. 步骤在这里插入图片描述
  2. 代码实现
 async login () {
      // 前端校验
      if (!this.validFn()) {
        return false
      }
      const res = await login(this.isParty, this.mobile, this.partyData, this.Mcode)
      console.log('已经登录了')

      // 将数据存储进store,更新vuex的数据
      this.$store.commit('user/setUserInfo', res.data)

      // 错误情况会有响应拦截器阻止,只需考虑正确情况即可
      this.$toast('登录成功')
      // 1. 地址携带查询参数,需要回跳
      // 2. 直接跳转到首页
      const url = this.$route.query.backUrl || '/'
      this.$router.replace(url)
    },

响应拦截器 - 统一处理错误提示

在这里插入图片描述

  1. 代码实现
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  const res = response.data
  if (res.status !== 200) {
    // 给错误提示,Toast默认是单例模式,后面的Toast调用了,会将前一个Toast效果覆盖
    // 同时只能存在一个Toast
    Toast(res.message)
    // 抛错误
    return Promise.reject(res.message)
  } else {
    Toast.clear()
  }
  return res
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error)
})

storage存储模块

  1. 需求在这里插入图片描述
  2. 代码实现
// 本地存储模块,利用本地存储进行vuex持久化处理

const InfoKey = 'pro-shooping'
const HistoryKey = 'pro-History'

export const setUserInfo = (UserInfo) => {
  localStorage.setItem(InfoKey, JSON.stringify(UserInfo))
}

export const getUserInfo = () => {
  const result = localStorage.getItem(InfoKey)
  return result ? JSON.parse(result) : { token: '', userId: '' }
}

export const removeInfo = () => {
  localStorage.removeItem(InfoKey)
}

export const getHistory = () => {
  const result = localStorage.getItem(HistoryKey)
  return result ? JSON.parse(result) : []
}

export const setHistory = (arr) => {
  localStorage.setItem(HistoryKey, JSON.stringify(arr))
}

登录权证信息存储 - vuex持久化存储

  1. 步骤在这里插入图片描述
  2. 构建user模块
// 存储 user 信息

// 导入storage模块,处理vuex持久化
import { setUserInfo, getUserInfo } from '@/utils/storage'
export default {
  namespaced: true,
  state () {
    return {
      userInfo: getUserInfo()
    }
  },
  mutations: {
    // mutations中的第一个形参都是state,第二个才是payload
    setUserInfo (state, obj) {
      console.log(obj)
      state.userInfo = obj
      setUserInfo(obj)
    }
  },
  actions: {
    logout (context) {
      context.commit('setUserInfo', {})
      // 第三个参数设置成root:true,可以将此方法挂载到全局上,可以按照全局使用
      context.commit('cart/setcartList', [], { root: true })
    }
  },
  getters: {}
}

  1. 挂载到vuex上
// 在index.js中导入
import user from './modules/user'
import cart from './modules/cart'

// 挂载
export default new Vuex.Store({
  state: {
  },
  getters: {
    token (state) {
      return state.user.userInfo.token
    }
  },
  mutations: {
  },
  actions: {
  },
  // 模块化
  modules: {
    user,
    cart
  }
})

添加请求loading效果

  1. 实操步骤
    在这里插入图片描述
  2. 代码实现
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  // 添加loading效果
  Toast.loading({
    message: '加载中...',
    forbidClick: true, // 禁止背景点击
    loadingType: 'spinner', //加载图标
    duration: 0 // 设置loding不会关闭
  })

页面访问拦截

  1. 需求在这里插入图片描述
  2. 路由导航守卫-全局前置守卫
    在这里插入图片描述
    在这里插入图片描述
// 创建一个需要访问权限页面的数组
const authArray = ['/pay', '/myOrder']
// 前置守卫
router.beforeEach((to, from, next) => {
  // 如果不是权限页面
  if (!authArray.includes(to.path)) {
    next()
    return
  }
  // 是需要权限的页面 ->判断是否有token凭证,有则放行,无则跳转到登录页面
  const token = store.getters.token
  console.log(token)
  if (token) {
    next()
  } else {
    next('/login')
  }
})

首页模块

静态页面-基于vant完成

  1. 步骤
    在这里插入图片描述

  2. 代码

<template>
  <div class="home">
    <!-- 导航条 -->
    <van-nav-bar title="智慧商城" fixed />

    <!-- 搜索框 -->
    <van-search
      readonly
      shape="round"
      background="#f1f1f2"
      placeholder="请在此输入搜索关键词"
      @click="$router.push('/search')"
    />

    <!-- 轮播图 -->
    <van-swipe class="my-swipe" :autoplay="3000" indicator-color="white">
      <van-swipe-item v-for="item in items[1].data" :key="item.imgUrl">
        <img :src="item.imgUrl" alt="">
      </van-swipe-item>
    </van-swipe>

    <!-- 导航 -->
    <van-grid column-num="5" icon-size="40">
      <van-grid-item
        v-for="item in items[3].data" :key="item.imgUrl"
        :icon="item.imgUrl"
        :text="item.text"
        @click="$router.push('/category')"
      />
    </van-grid>

    <!-- 主会场 -->
    <div class="main">
      <img src="@/assets/main.png" alt="">
    </div>

    <!-- 猜你喜欢 -->
    <div class="guess">
      <p class="guess-title">—— 猜你喜欢 ——</p>

      <div class="goods-list">
        <GoodsItem v-for="(item) in items[6].data" :key="item.goods_id" :item="item" ></GoodsItem>
      </div>
    </div>
  </div>
</template>

<script>
import GoodsItem from '@/components/GoodsItem.vue'
import { getHomeData } from '@/api/home'
export default {
  name: 'HomePage',
  data () {
    return {
      items: []
    }
  },
  components: {
    GoodsItem
  },
  async created () {
    const res = await getHomeData()
    console.log(res)
    this.items = res.data.pageData.items
    console.log(this.items[6].data)
  }
}
</script>

<style lang="less" scoped>
// 主题 padding
.home {
  padding-top: 100px;
  padding-bottom: 50px;
}

// 导航条样式定制
.van-nav-bar {
  z-index: 999;
  background-color: #c21401;
  ::v-deep .van-nav-bar__title {
    color: #fff;
  }
}

// 搜索框样式定制
.van-search {
  position: fixed;
  width: 100%;
  top: 46px;
  z-index: 999;
}

// 分类导航部分
.my-swipe .van-swipe-item {
  height: 185px;
  color: #fff;
  font-size: 20px;
  text-align: center;
  background-color: #39a9ed;
}
.my-swipe .van-swipe-item img {
  width: 100%;
  height: 185px;
}

// 主会场
.main img {
  display: block;
  width: 100%;
}

// 猜你喜欢
.guess .guess-title {
  height: 40px;
  line-height: 40px;
  text-align: center;
}

// 商品样式
.goods-list {
  background-color: #f6f6f6;
}
</style>

搜索模块

搜索-历史记录管理

  1. 需求
    在这里插入图片描述
  2. 核心js代码
<script>
import { getHistory, setHistory } from '@/utils/storage'

export default {
  name: 'SearchIndex',
  data () {
    return {
      search: '', //和搜索框双向绑定
      list: getHistory() || [] //优先从本地拿取数据
    }
  },
  methods: {
    goSearch (key) {
      // indexOf 是用来查询数组中是否包含某个值,若有则返回该值的下标,否则返回-1
      const index = this.list.indexOf(key)
      if (index !== -1) {
        // 搜索的是数组中存在的值,则将其删除
        this.list.splice(index, 1)
      }
      // 将key加入数组首位
      this.list.unshift(key)
      // 更新本地存储
      setHistory(this.list)
      // this.search = ''
      this.$router.push(`/searchlist?key=${key}`)
    },
    clear () {
      this.list = []
      setHistory([])
    }
  }
}
</script>

搜索列表 - 静态布局&动态渲染

  1. 步骤
    在这里插入图片描述
  2. 核心js逻辑
<script>
import GoodsItem from '@/components/GoodsItem.vue'
import { getProList } from '@/api/product'
export default {
  name: 'SearchIndex',
  components: {
    GoodsItem
  },
  data () {
    return {
      proList: [],
      sortType: 'all',
      categoryId: ''
    }
  },
  // 使用计算属性来动态获取地址查询参数
  computed: {
    searchKey () {
    // 查询参数
      return this.$route.query.key
    }
  },
  async created () {
    this.getProList()
  },
  methods: {
    // 获取商品列表
    async getProList () {
      const res = await getProList({
        sortType: this.sortType,
        categoryId: this.$route.query.categoryId,
        goodsName: this.searchKey,
        page: 1
      })
      const { data: { list } } = res
      this.proList = list.data
    },
    // 商品排序
    async choice (sortType) {
      this.sortType = sortType
      this.getProList()
    }
  }
}
</script>

商品详情

  1. 步骤在这里插入图片描述
  2. 核心js逻辑
<script>
import { getGoodsComment, getGoodsDetail } from '@/api/product'
import CountBox from '@/components/CountBox.vue'
import defaultImage from '@/assets/default-avatar.png'
import { cartAdd } from '@/api/cart'
import loginConfirm from '@/mixins/loginConfirm'
export default {
  name: 'ProDetail',
  mixins: [loginConfirm],
  components: {
    CountBox
  },
  data () {
    return {
      images: [],
      current: 0,
      detail: {},
      list: [],
      total: '',
      showPannel: false,
      mode: 'cart', // 控制弹窗
      goodsNum: 1,
      defaultImage,
      cartTotal: ''
    }
  },
  created () {
    this.goodsDetail()
    this.goodsComment()
  },
  computed: {
    getGoodsId () {
      return this.$route.params.id
    }
  },
  methods: {
    onChange (index) {
      this.current = index
    },
    // 获取商品详情
    async goodsDetail () {
      const res = await getGoodsDetail(this.getGoodsId)
      this.detail = res.data.detail
      this.images = this.detail.goods_images
      console.log(this.detail)
    },
    // 获取商品评论
    async goodsComment () {
      const res = await getGoodsComment(this.getGoodsId, 3)
      console.log(res)
      this.total = res.data.total
      this.list = res.data.list
    },

    // 购物车
    cartAdd () {
      this.showPannel = true
      this.mode = 'cart'
    },
    buynow () {
      this.showPannel = true
      this.mode = ''
    }
</script>
  1. 在请求拦截器里设置请求头参数
  // 在config中携带请求头参数
  const token = store.getters.token
  if (token) {
    config.headers['Access-Token'] = token
    config.headers.platform = 'H5'
  }

购物车模块

购物车弹层-vant

 <van-action-sheet v-model="showPannel" :title="mode === 'cart' ? '加入购物车' : '立刻购买'">
      <div class="product">
        <div class="product-title">
          <div class="left">
            <img :src="detail.goods_image" alt="">
          </div>
          <div class="right">
            <div class="price">
              <span>¥</span>
              <span class="nowprice">{{detail.goods_price_min}}</span>
            </div>
            <div class="count">
              <span>库存</span>
              <span>{{detail.stock_total}}</span>
            </div>
          </div>
        </div>
        <div class="num-box">
          <span>数量</span>
          <CountBox v-model="goodsNum"></CountBox>
        </div>
        <div class="showbtn" v-if="detail.stock_total>0 ">
          <div @click="AddCart" class="btn" v-if="mode">加入购物车</div>
          <div @click="buyNow" class="btn now" v-else>立刻购买</div>
        </div>
        <div class="btn-none" v-else>该商品已抢完</div>
      </div>
    </van-action-sheet>

封装数字框组件

  1. 步骤
    在这里插入图片描述
  2. 代码实现
<template>
  <div class="countBox">
    <div class="left" @click="countSub">-</div>
    <input :value="value" @change="inpChange" class="inp" type="text">
    <div class="right" @click="countAdd">+</div>
  </div>
</template>

<script>
export default {
  // 通过props接收
  props: {
    value: {
      type: Number,
      default: 1
    }
  },
  methods: {
    countSub () {
      if (this.value <= 1) {
        return
      }
      // 子传父
      this.$emit('input', this.value - 1)
    },
    countAdd () {
      this.$emit('input', this.value + 1)
    },
    inpChange (e) {
      const num = +e.target.value
      // 输入不合法的情况下
      if (isNaN(num) || num <= 0) {
        e.target.value = this.value
        return
      }
      this.$emit('input', num)
    }
  }
}
</script>

<style lang="less" scoped>
    .countBox{
        width: 110px;
        height: 30px;
        display: flex;
        font-size: 15px;
        .left,.right{
            width: 30px;
            height: 30px;
            background-color: #efefef;
            outline: none;
            border: none;
            text-align: center;
            line-height: 30px;
        }
        .inp{
            width: 40px;
            height: 30px;
            margin: 0px 5px;
            background-color: #efefef;
            outline: none;
            border: none;
             text-align: center;
            line-height: 30px;
        }
    }
</style>

判断token添加登录提示

  1. 步骤
    在这里插入图片描述
  2. 代码实现
async AddCart () {
      // 判断token是否存在
      if (this.loginConfirm()) {
        return
      }
      // 若有token则需要发送请求
      // 遇到需要携带请求头的参数,直接前往请求拦截器设置
      const res = await cartAdd(this.getGoodsId, this.goodsNum, this.detail.skuList[0].goods_sku_id)
      console.log(res)
      this.cartTotal = res.data.cartTotal
      this.$toast('加入购物车成功')
    },

路由回跳

在这里插入图片描述

封装接口进行请求

  1. 步骤
    在这里插入图片描述
  2. 静态结构
<template>
  <div class="cart">
    <van-nav-bar title="购物车" fixed />
    <!-- 购物车开头 -->
    <div class="cart-title">
      <span class="all"><i>4</i>件商品</span>
      <span class="edit">
        <van-icon name="edit" />
        编辑
      </span>
    </div>

    <!-- 购物车列表 -->
    <div class="cart-list">
      <div class="cart-item" v-for="item in 10" :key="item">
        <van-checkbox></van-checkbox>
        <div class="show">
          <img src="http://cba.itlike.com/public/uploads/10001/20230321/a072ef0eef1648a5c4eae81fad1b7583.jpg" alt="">
        </div>
        <div class="info">
          <span class="tit text-ellipsis-2">新Pad 14英寸 12+128 远峰蓝 M6平板电脑 智能安卓娱乐十核游戏学习二合一 低蓝光护眼超清4K全面三星屏5GWIFI全网通 蓝魔快本平板</span>
          <span class="bottom">
            <div class="price">¥ <span>1247.04</span></div>
            <div class="count-box">
              <button class="minus">-</button>
              <input class="inp" :value="4" type="text" readonly>
              <button class="add">+</button>
            </div>
          </span>
        </div>
      </div>
    </div>

    <div class="footer-fixed">
      <div  class="all-check">
        <van-checkbox  icon-size="18"></van-checkbox>
        全选
      </div>

      <div class="all-total">
        <div class="price">
          <span>合计:</span>
          <span>¥ <i class="totalPrice">99.99</i></span>
        </div>
        <div v-if="true" class="goPay">结算(5)</div>
        <div v-else class="delete">删除</div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'CartPage'
}
</script>

<style lang="less" scoped>
// 主题 padding
.cart {
  padding-top: 46px;
  padding-bottom: 100px;
  background-color: #f5f5f5;
  min-height: 100vh;
  .cart-title {
    height: 40px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 10px;
    font-size: 14px;
    .all {
      i {
        font-style: normal;
        margin: 0 2px;
        color: #fa2209;
        font-size: 16px;
      }
    }
    .edit {
      .van-icon {
        font-size: 18px;
      }
    }
  }

  .cart-item {
    margin: 0 10px 10px 10px;
    padding: 10px;
    display: flex;
    justify-content: space-between;
    background-color: #ffffff;
    border-radius: 5px;

    .show img {
      width: 100px;
      height: 100px;
    }
    .info {
      width: 210px;
      padding: 10px 5px;
      font-size: 14px;
      display: flex;
      flex-direction: column;
      justify-content: space-between;

      .bottom {
        display: flex;
        justify-content: space-between;
        .price {
          display: flex;
          align-items: flex-end;
          color: #fa2209;
          font-size: 12px;
          span {
            font-size: 16px;
          }
        }
        .count-box {
          display: flex;
          width: 110px;
          .add,
          .minus {
            width: 30px;
            height: 30px;
            outline: none;
            border: none;
          }
          .inp {
            width: 40px;
            height: 30px;
            outline: none;
            border: none;
            background-color: #efefef;
            text-align: center;
            margin: 0 5px;
          }
        }
      }
    }
  }
}

.footer-fixed {
  position: fixed;
  left: 0;
  bottom: 50px;
  height: 50px;
  width: 100%;
  border-bottom: 1px solid #ccc;
  background-color: #fff;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 10px;

  .all-check {
    display: flex;
    align-items: center;
    .van-checkbox {
      margin-right: 5px;
    }
  }

  .all-total {
    display: flex;
    line-height: 36px;
    .price {
      font-size: 14px;
      margin-right: 10px;
      .totalPrice {
        color: #fa2209;
        font-size: 18px;
        font-style: normal;
      }
    }

    .goPay, .delete {
      min-width: 100px;
      height: 36px;
      line-height: 36px;
      text-align: center;
      background-color: #fa2f21;
      color: #fff;
      border-radius: 18px;
      &.disabled {
        background-color: #ff9779;
      }
    }
  }

}
</style>
  1. 构建vuex的cart模块并动态渲染
import { getCartList, delSelect } from '@/api/cart'
import { Toast } from 'vant'
export default {
  namespaced: true,
  state () {
    return {
      cartList: []
    }
  },
  mutations: {
	setcartList (state, newList) {
      state.cartList = newList
    },
},
  actions: {
  	   // 异步获取数据,通过mutations修改购物车数据
    async getcartActions (context) {
      const res = await getCartList()
      // 挂在ischecked来控制复选框
      const { data } = res
      data.list.forEach(item => {
        item.ischecked = false
      })
      console.log(res)
      context.commit('setcartList', data.list)
   },
  getters: {}

  1. 封装getters实现动态统计
 getters: {
    // 统计商品数量
    countTotal (state) {
      return state.cartList.reduce((sum, item) => sum + item.goods_num, 0)
    },

    // 统计选中的商品项
    selProduct (state) {
      return state.cartList.filter(item => item.ischecked)
    },

    // 统计选中的商品数量
    // 在getters中可以设置第二个参数使用其他的getters
    selCount (state, getters) {
      return getters.selProduct.reduce((sum, item) => sum + item.goods_num, 0)
    },
    // 统计选中的商品总价
    selPrice (state, getters) {
      return getters.selProduct.reduce((sum, item) => sum + item.goods_num * item.goods.goods_price_min, 0)
    },

    // 设置是否全选
    isAllCheck (state) {
      return state.cartList.every(item => item.ischecked)
    }
  }
}
  1. 全选反选功能实现
// 1. 因为后返回的数据没有ischecked属性来标识商品
// 所以我们需要前端手动的加上此属性来标识
  // 异步获取数据,通过mutations修改购物车数据
    async getcartActions (context) {
      const res = await getCartList()
      // 挂在ischecked来控制复选框
      const { data } = res
      data.list.forEach(item => {
        item.ischecked = false
      })
      console.log(res)
      context.commit('setcartList', data.list)
    },
// 2. 提供mutations函数,完成全选反选功能
// 状态取反
    toggleCheck (state, goodsId) {
      const goods = state.cartList.find(item => item.goods_id === goodsId)
      goods.ischecked = !goods.ischecked
    },
    // 全选反选
    toggleAllCheck (state, flag) {
      state.cartList.forEach(item => {
        item.ischecked = flag
      })
    },
  1. 数字框修改数量功能
 changeCount (state, obj) {
      const { goodsId, goodsNum } = obj
      const goods = state.cartList.find(item => item.goods_id === goodsId)
      goods.goods_num = goodsNum
    }
  1. 删除购物车
 // 删除购物车 -- 需要选中的商品的id发送请求
    async delSelectActions (context) {
      // 通过getters拿到被选中的商品
      const selGoods = context.getters.selProduct
      // 通过map遍历被选中的商品,拿到其中的id值
      const cartIds = selGoods.map((item) => item.id)
      console.log(cartIds)
      const res = await delSelect(cartIds)
      console.log(res)
      Toast('删除成功')
      // 删除成功,重新渲染
      context.dispatch('getcartActions')
    }

订单结算模块

购物车结算

在这里插入图片描述

立刻购买结算

在这里插入图片描述

mixins混入

  1. 使用时只需要在组件内引用即可
export default {
  data () {
    return {

    }
  },
  methods: {
    loginConfirm () {
      // 因为token事先存放在了store仓库里的getters里
      // 若是没有token,则需要跳转到登录页
      if (!this.$store.getters.token) {
        // 使用一个dialog弹出框
        this.$dialog.confirm({
          title: '温馨提示',
          message: '此时需要先登录才能继续操作哦',
          confirmButtonText: '去登录',
          cancelButtonText: '在逛逛'
        })
        // 点击确定按钮走then操作,此时需要跳转到登录界面
          .then(() => {
            // 如果希望跳转到登录 =》登录完跳转回来,就必须携带参数(当前的路径地址)
            // this.$route.fullpath(会包含查询参数)
            // replace 与 push 的区别在于不会产生新的历史记录,而是直接替换即可
            this.$router.replace({
              path: '/login',
              query: {
                backUrl: this.$route.fullPath
              }
            })
          })
          .catch(() => {
            // on cancel
          })
        return true
      }
      return false
    }
  }
}

提交订单并支付

在这里插入图片描述

跨模块调用actions

在这里插入图片描述

打包

打包发布

在这里插入图片描述
在这里插入图片描述

打包优化:路由懒加载

在这里插入图片描述

import search from '@/views/search'
import searchList from '@/views/search/list'

import myOrder from '@/views/myOrder'
import store from '@/store/index'
import address from '@/views/address'

// 二级路由
import homePage from '@/views/layout/homePage'
import cartPage from '@/views/layout/cartPage'
import catecategoryPageory from '@/views/layout/categoryPage'
import userPage from '@/views/layout/userPage'

const login = () => import('@/views/login')
const goodsDetail = () => import('@/views/goodsDetail')
const pay = () => import('@/views/pay')
const update = () => import('@/views/address/update')
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值