60 - 综合案例 - 智慧商城-12 - 订单结算台

一. 订单结算台

说明:所有的结算,本质上就是 跳转到"订单结算台",并且,跳转的同时,需要 携带上对应的订单相关参数

具体需要哪些参数,基于“订单结算台”的需求来定。

1. 静态布局

        views / pay / index.vue

<template>
  <div class="pay">
    <van-nav-bar fixed title="订单结算台" left-arrow @click-left="$router.go(-1)" />

    <!-- 地址相关 -->
    <div class="address">

      <div class="left-icon">
        <van-icon name="logistics" />
      </div>

      <div class="info" v-if="true">
        <div class="info-content">
          <span class="name">小红</span>
          <span class="mobile">13811112222</span>
        </div>
        <div class="info-address">
          江苏省 无锡市 南长街 110号 504
        </div>
      </div>

      <div class="info" v-else>
        请选择配送地址
      </div>

      <div class="right-icon">
        <van-icon name="arrow" />
      </div>
    </div>

    <!-- 订单明细 -->
    <div class="pay-list">
      <div class="list">
        <div class="goods-item">
            <div class="left">
              <img src="http://cba.itlike.com/public/uploads/10001/20230321/8f505c6c437fc3d4b4310b57b1567544.jpg" alt="" />
            </div>
            <div class="right">
              <p class="tit text-ellipsis-2">
                 三星手机 SAMSUNG Galaxy S23 8GB+256GB 超视觉夜拍系统 超清夜景 悠雾紫 5G手机 游戏拍照旗舰机s23
              </p>
              <p class="info">
                <span class="count">x3</span>
                <span class="price">¥9.99</span>
              </p>
            </div>
        </div>
      </div>

      <div class="flow-num-box">
        <span>共 12 件商品,合计:</span>
        <span class="money">¥1219.00</span>
      </div>

      <div class="pay-detail">
        <div class="pay-cell">
          <span>订单总金额:</span>
          <span class="red">¥1219.00</span>
        </div>

        <div class="pay-cell">
          <span>优惠券:</span>
          <span>无优惠券可用</span>
        </div>

        <div class="pay-cell">
          <span>配送费用:</span>
          <span v-if="false">请先选择配送地址</span>
          <span v-else class="red">+¥0.00</span>
        </div>
      </div>

      <!-- 支付方式 -->
      <div class="pay-way">
        <span class="tit">支付方式</span>
        <div class="pay-cell">
          <span><van-icon name="balance-o" />余额支付(可用 ¥ 999919.00 元)</span>
          <!-- <span>请先选择配送地址</span> -->
          <span class="red"><van-icon name="passed" /></span>
        </div>
      </div>

      <!-- 买家留言 -->
      <div class="buytips">
        <textarea placeholder="选填:买家留言(50字内)" name="" id="" cols="30" rows="10"></textarea>
      </div>
    </div>

    <!-- 底部提交 -->
    <div class="footer-fixed">
      <div class="left">实付款:<span>¥999919</span></div>
      <div class="tipsbtn">提交订单</div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'PayIndex',
  data () {
    return {
    }
  },
  methods: {
  }
}
</script>

<style lang="less" scoped>
.pay {
  padding-top: 46px;
  padding-bottom: 46px;
  ::v-deep {
    .van-nav-bar__arrow {
      color: #333;
    }
  }
}
.address {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding: 20px;
  font-size: 14px;
  color: #666;
  position: relative;
  background: url(@/assets/121.png) bottom repeat-x;
  background-size: 60px auto;
  .left-icon {
    margin-right: 20px;
  }
  .right-icon {
    position: absolute;
    right: 20px;
    top: 50%;
    transform: translateY(-7px);
  }
}
.goods-item {
  height: 100px;
  margin-bottom: 6px;
  padding: 10px;
  background-color: #fff;
  display: flex;
  .left {
    width: 100px;
    img {
      display: block;
      width: 80px;
      margin: 10px auto;
    }
  }
  .right {
    flex: 1;
    font-size: 14px;
    line-height: 1.3;
    padding: 10px;
    padding-right: 0px;
    display: flex;
    flex-direction: column;
    justify-content: space-evenly;
    color: #333;
    .info {
      margin-top: 5px;
      display: flex;
      justify-content: space-between;
      .price {
        color: #fa2209;
      }
    }
  }
}

.flow-num-box {
  display: flex;
  justify-content: flex-end;
  padding: 10px 10px;
  font-size: 14px;
  border-bottom: 1px solid #efefef;
  .money {
    color: #fa2209;
  }
}

.pay-cell {
  font-size: 14px;
  padding: 10px 12px;
  color: #333;
  display: flex;
  justify-content: space-between;
  .red {
    color: #fa2209;
  }
}
.pay-detail {
  border-bottom: 1px solid #efefef;
}

.pay-way {
  font-size: 14px;
  padding: 10px 12px;
  border-bottom: 1px solid #efefef;
  color: #333;
  .tit {
    line-height: 30px;
  }
  .pay-cell {
    padding: 10px 0;
  }
  .van-icon {
    font-size: 20px;
    margin-right: 5px;
  }
}

.buytips {
  display: block;
  textarea {
    display: block;
    width: 100%;
    border: none;
    font-size: 14px;
    padding: 12px;
    height: 100px;
  }
}

.footer-fixed {
  position: fixed;
  background-color: #fff;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 46px;
  line-height: 46px;
  border-top: 1px solid #efefef;
  font-size: 14px;
  display: flex;
  .left {
    flex: 1;
    padding-left: 12px;
    color: #666;
    span {
      color:#fa2209;
    }
  }
  .tipsbtn {
    width: 121px;
    background: linear-gradient(90deg,#f9211c,#ff6335);
    color: #fff;
    text-align: center;
    line-height: 46px;
    display: block;
    font-size: 14px;
  }
}
</style>

二. 订单结算台-获取收货地址列表

1. 封装获取地址接口

        api / address.js

import request from '@/utils/request'

// 获取地址列表
export const getAddressList = () => {
  return request.get('/address/list')
}
2. 页面中调用获取地址

        views / pay / index.vue

<script>
import { getAddressList } from '@/api/address'
export default {
  name: 'PayIndex',
  data () {
    return {
      addressList: [] //定义空列表
    }
  },
  computed: {
    selectAddress () {
      // 这里地址管理非主线业务,直接获取第一个项作为选中的地址
      return this.addressList[0] || {}
    },
    longAddress () {
      const region = this.selectAddress.region
      // 拼接详细地址
      return region.province + region.city + region.region + this.selectAddress.detail
    }
  },
  created () {
    this.getAddressLists()
  },
  methods: {
    async getAddressLists () {
      const { data: { list } } = await getAddressList() // 调用地址请求接口
      this.addressList = list
      console.log(list)
    }
  }
}
</script>
3. 页面中 - 进行渲染

        views / pay / index.vue

 <div class="info" v-if="selectAddress.address_id">
        <div class="info-content">
          <span class="name">{{ selectAddress.name }}</span>
          <span class="mobile">{{ selectAddress.phone }}</span>
        </div>
        <div class="info-address">
          {{ longAddress }}
        </div>
 </div>

三. 订单结算台- 确认订单信息

目标:封装通用的订单信息确认接口

说明:这里的订单信息确认结算,有两种情况
        (1). 购物车结算
        (2). 立即购买结算

订单信息确认,可以共用同一个接口(参数不同)

1. 封装通用接口

        api / order.js

import request from '@/utils/request'

// 订单结算确认
// mode: cart => obj { cartId }
// mode: buyNow => { obj goodsId goodsNum goodsSkuId }
export const checkOrder = (mode, obj) => {
  return request.get('/checkout/order', {
    params: {
      mode, // cart  buyNow
      delivery: 10, // 10:快递配送 20:门店自提
      couponId: 0, // 优惠券ID 传0 不使用优惠券
      isUsePoints: 0, // 积分 传0 不使用积分
      ...obj // 将传递过来的参数对象 动态展开
    }
  })
}

四. 订单结算台-购物车结算

目标:购物车结算跳转,传递参数,调用接口渲染订单结算台

核心步骤:
        (1). 跳转传递查询参数 mode="cart"和 cartlds
        (2). 页面中 $route.query 接收参数
        (3). 调用接口,获取数据
        (4). 基于数据渲染

        

1. 跳转时,传递查询参数

         views / layout / cart.vue

<template>
  <div v-if="!isEdit" class="goPay" :class="{ disabled: selCount === 0 }" @click="goPay">结算({{ selCount }})</div>
</template>

<script>
 methods: {
    ...
    goPay () {
      // 判断有没有选中商品
      if (this.selCount > 0) {
        // 有选中的 商品 才进行结算跳转
        this.$router.push({
          path: '/pay',
          query: {
            mode: 'cart',
            cartIds: this.selCartList.map(item => item.id).join(',') // 'cartId,cartId,cartId'
          }
        })
      }
    }
  },
</script>
2. 页面中接收参数, 调用接口,获取数据

       views / pay / index.vue

<script>
import { getAddressList } from '@/api/address'
import { checkOrder } from '@/api/order'
export default {
  name: 'PayIndex',
  data () {
    return {
      addressList: [],
      order: {},
      personal: {}
    }
  },
  computed: {
    ...
    mode () {
      return this.$route.query.mode
    },
    cartIds () {
      return this.$route.query.cartIds
    }
  },
  created () {
    ...
    this.getOrderLists()
  },
  methods: {
    ...
    async getOrderLists () {
      const { data: { order, personal } } = await checkOrder(this.mode, {
        cartIds: this.cartIds
      })
      this.order = order
      this.personal = personal
    }
  }
}
</script>
3.  基于数据进行渲染

        views / pay / index.vue

<!-- 订单明细 -->
<div class="pay-list" v-if="order.goodsList">
  <div class="list">
    <div class="goods-item" v-for="item in order.goodsList" :key="item.goods_id">
        <div class="left">
          <img :src="item.goods_image" alt="" />
        </div>
        <div class="right">
          <p class="tit text-ellipsis-2">
            {{ item.goods_name }}
          </p>
          <p class="info">
            <span class="count">x{{ item.total_num }}</span>
            <span class="price">¥{{ item.total_pay_price }}</span>
          </p>
        </div>
    </div>
  </div>

  <div class="flow-num-box">
    <span>共 {{ order.orderTotalNum }} 件商品,合计:</span>
    <span class="money">¥{{ order.orderTotalPrice }}</span>
  </div>

  <div class="pay-detail">
    <div class="pay-cell">
      <span>订单总金额:</span>
      <span class="red">¥{{ order.orderTotalPrice }}</span>
    </div>

    <div class="pay-cell">
      <span>优惠券:</span>
      <span>无优惠券可用</span>
    </div>

    <div class="pay-cell">
      <span>配送费用:</span>
      <span v-if="!selectAddress">请先选择配送地址</span>
      <span v-else class="red">+¥0.00</span>
    </div>
  </div>

  <!-- 支付方式 -->
  <div class="pay-way">
    <span class="tit">支付方式</span>
    <div class="pay-cell">
      <span><van-icon name="balance-o" />余额支付(可用 ¥ {{ personal.balance }} 元)</span>
      <!-- <span>请先选择配送地址</span> -->
      <span class="red"><van-icon name="passed" /></span>
    </div>
  </div>

  <!-- 买家留言 -->
  <div class="buytips">
    <textarea placeholder="选填:买家留言(50字内)" name="" id="" cols="30" rows="10"></textarea>
  </div>
</div>

<!-- 底部提交 -->
<div class="footer-fixed">
  <div class="left">实付款:<span>¥{{ order.orderTotalPrice }}</span></div>
  <div class="tipsbtn">提交订单</div>
</div>

五. 订单结算台-立即购买结算

目标: 购物车结算跳转,传递参数,调用接口渲染订单结算台

核心步骤:

        (1). 跳转传递查询参数
                mode="buyNow",goodsld, goodsSkuld,goodsNum
        (2). 页面中 $route.query 接收参数
        (3). 调用接口,获取数据
        (4). 基于数据渲染
        (5). 未登录时,确认框的复用(mixins混入)

1. 点击跳转传参

        views / prodetail / index.vue

<template>
<div class="btn now" v-else @click="goBuyNow">立刻购买</div>
</template>

<script>
 methods: {
    ...
    goBuyNow () {
      this.$router.push({
        path: '/pay',
        query: {
          mode: 'buyNow',
          goodsId: this.goodsId,
          goodsSkuId: this.detail.skuList[0].goods_sku_id,
          goodsNum: this.addCount
        }
      })
    }
  }
</script>
2. 计算属性处理参数

       views / pay / index.vue

computed: {
    ...
    goodsId () {
      return this.$route.query.goodsId
    },
    goodsSkuId () {
      return this.$route.query.goodsSkuId
    },
    goodsNum () {
      return this.$route.query.goodsNum
    }

  },
3. 基于请求时携带参数发请求渲染

         views / pay / index.vue

 methods: {
    ...
    async getOrderLists () {
      // 购物车结算
      if (this.mode === 'cart') {
        const { data: { order, personal } } = await checkOrder(this.mode, {
          cartIds: this.cartIds
        })
        this.order = order
        this.personal = personal
      }
      // 立刻购买结算
      if (this.mode === 'buyNow') {
        const { data: { order, personal } } = await checkOrder(this.mode, {
          goodsId: this.goodsId,
          goodsSkuId: this.goodsSkuId,
          goodsNum: this.goodsNum

        })
        this.order = order
        this.personal = personal
      }
    }
  }
4. mixins 复用 - 处理登录确认框的弹出

        (1). 建立一个mixin文件,封装登录验证

                src / mixins / loginConfirm.js

export default {
  // 此处编写的就是 Vue组件实例的 配置项 通过一定语法,可以直接混入到组件内部
  // data method computed 生命周期函数 ...
  // 注意点:
  //   1. 如果此处 和 组件内,提供了同名的 data 或 methods, 则组件内优先级更高
  //   2. 如果编写了生命周期函数,则mixins中的生命周期函数 和 页面中的生命周期函数,
  //      会用数组管理,统一执行
  methods: {
    // 根据登录状态,判断是否需要显示登录确认框
    // 1. 如果未登录 => 显示确认框 返回true
    // 2. 如果已登录 => 啥也不干  返回false
    loginConfirm () {
      // 判断 token是否存在
      if (!this.$store.getters.token) {
        // 弹确认框
        this.$dialog.confirm({
          title: '温馨提示',
          message: '此时需要先登录才能继续操作哦',
          confirmButtonText: '去登录',
          cancelButtonText: '在逛逛'
        })
          .then(() => {
            // 如果希望, 跳转到登录 => 登录后能回跳回来,需要在跳转去携带参数(当前的路径地址)
            // this.$route.fullPath(会包含查询参数)
            // replace: 跳转路由,会将上一个replace路由替换成本次replace路由
            this.$router.replace({
              path: '/login',
              // 额外携带参数
              query: {
                backUrl: this.$route.fullPath
              }
            })
          })
          .catch(() => {})
        return true
      }
      return false
    }
  }
}

        (2). 页面中导入,混入方法

               views / prodetail / index.vue

<script>
import loginConfirm from '@/mixins/loginConfirm'
export default {
  name: 'ProDetail',
  mixins: [loginConfirm], // 混入
  ...
}
</script>

        (3). 页面中调用混入方法

                     views / prodetail / index.vue

<script>
methods:{
  async addCart () {
      // 1. 如果token不存在, 弹确认框
      // 2. 如果token存在, 继续请求操作
      if (this.loginConfirm()) {
        return
      }

      // console.log('正常请求')
      //const { data } = await addCart(this.goodsId, this.addCount, this.detail.skuList[0].goods_sku_id)
      //this.cartTotal = data.cartTotal
      //this.$toast('加入购物车成功')
     // this.showPannel = false // 关闭弹层
      //console.log(this.cartTotal)
    },
    goBuyNow () {
      // 未登录的处理: 需要弹出一个确认框
      // 您当前的操作需要登录才能继续 => 去登录 在逛逛
      // 判断token 是否存在
      if (this.loginConfirm()) {
        return
      }

      //this.$router.push({
      //  path: '/pay',
      //  query: {
       //   mode: 'buyNow',
       //   goodsId: this.goodsId,
        //  goodsSkuId: this.detail.skuList[0].goods_sku_id,
         // goodsNum: this.addCount
      //  }
     // })
    //}
}
</script>

六. 提交订单并支付

目标:封装 API 请求方法,提交订单并支付

核心步骤:

        (1). 封装通用请求方法
        (2). 买家留言绑定
        (3). 注册事件,调用方法提交订单并支付

1. 封装API通用方法(统一余额支付)

       src / api / order.js

import request from '@/utils/request'
...

// 提交订单
// mode: cart => obj { cartId, remark }
// mode: buyNow => { obj goodsId goodsNum goodsSkuId,remark }
export const submitOrder = (mode, obj) => {
  return request.post('/checkout/submit', {
    mode, // cart  buyNow
    delivery: 10, // 10:快递配送 20:门店自提
    couponId: 0, // 优惠券ID 传0 不使用优惠券
    isUsePoints: 0, // 积分 传0 不使用积分
    payType: 10, // 余额支付
    ...obj // 将传递过来的参数对象 动态展开
  })
}
2. 买家留言绑定

       views / pay / index.vue

<template>
    <!-- 买家留言 -->
    <div class="buytips">
        <textarea v-model="remake" placeholder="选填:买家留言(50字内)" name="" id="" cols="30" rows="10"></textarea>
    </div>
</template>

<script>
data () {
    return {
      ...
      remake: '' // 备注留言
    }
</script>
3. 注册点击事件,提交订单并支付

           views / pay / index.vue

<template>
    <div class="tipsbtn" @click="submitOrders">提交订单</div>
</template>


<script>
import { checkOrder, submitOrder } from '@/api/order'

  methods: {
    // 提交订单
    async submitOrders () {
      if (this.mode === 'cart') {
        await submitOrder(this.mode, {
          cartIds: this.cartIds,
          remake: this.remake
        })
      }
      if (this.mode === 'buyNow') {
        await submitOrder(this.mode, {
          remark: this.remark,
          goodsId: this.goodsId,
          goodsSkuId: this.goodsSkuId,
          goodsNum: this.goodsNum
        })
      }
      this.$toast.success('支付成功')
      this.$router.replace('/myorder')
    },
}
</script>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值