09. 智慧商城——订单结算、订单管理

01. 订单结算台

所谓的 “立即结算”,本质就是跳转到订单结算台,并且跳转的同时,需要携带上对应的订单参数。

而具体需要哪些参数,就需要基于 【订单结算台】 的需求来定。

(1) 静态布局

09. 智慧商城——订单结算、订单管理_第1张图片

准备静态页面

<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">13811112222span>
        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">x3span>
                <span class="price">¥9.99span>
              p>
            div>
        div>
      div>

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

      <div class="pay-detail">
        <div class="pay-cell">
          <span>订单总金额:span>
          <span class="red">¥1219.00span>
        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.00span>
        div>
      div>

      
      <div class="pay-way">
        <span class="tit">支付方式span>
        <div class="pay-cell">
          <span><van-icon name="balance-o" />余额支付(可用 ¥ 999919.00 元)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>¥999919span>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/border-line.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>

(2) 获取收货地址列表

1 封装获取地址的接口

import request from '@/utils/request'

// 获取地址列表
export const getAddressList = () => {
  return request.get('/address/list')
}

2 页面中 - 调用获取地址

data () {
  return {
    addressList: []
  }
},
computed: {
  selectAddress () {
    // 这里地址管理不是主线业务,直接获取默认第一条地址
    return this.addressList[0] 
  }
},
async created () {
  this.getAddressList()
},
methods: {
  async getAddressList () {
    const { data: { list } } = await getAddressList()
    this.addressList = list
  }
}

3 页面中 - 进行渲染

computed: {
  longAddress () {
    const region = this.selectAddress.region
    return region.province + region.city + region.region + this.selectAddress.detail
  }
},

<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>

(3) 订单结算 - 封装通用接口

思路分析: 这里的订单结算,有两种情况:

  1. 购物车结算,需要两个参数

    ① mode=“cart”

    ② cartIds=“cartId, cartId”

  2. 立即购买结算,需要三个参数

    ① mode=“buyNow”

    ② goodsId=“商品id”

    ③ goodsSkuId=“商品skuId”【商品规格,如大小、颜色】

都需要跳转时将参数传递过来


封装通用 API 接口 api/order

import request from '@/utils/request'

export const checkOrder = (mode, obj) => {
  return request.get('/checkout/order', {
    params: {
      mode,
      delivery: 0,
      couponId: 0,
      isUsePoints: 0,
      ...obj
    }
  })
}

(4) 订单结算 - 购物车结算

1 跳转时,传递查询参数

layout/cart.vue

<div @click="goPay">结算({{ selCount }})</div>

goPay () {
  if (this.selCount > 0) {
    this.$router.push({
      path: '/pay',
      query: {
        mode: 'cart',
        cartIds: this.selCartList.map(item => item.id).join(',')
      }
    })
  }
}

2 页面中接收参数, 调用接口,获取数据

data () {
  return {
    order: {},
    personal: {}
  }
},
    
computed: {
  mode () {
    return this.$route.query.mode
  },
  cartIds () {
    return this.$route.query.cartIds
  }
}

async created () {
  this.getOrderList()
},

async getOrderList () {
  if (this.mode === 'cart') {
    const { data: { order, personal } } = await checkOrder(this.mode, { cartIds: this.cartIds })
    this.order = order
    this.personal = personal
  }
}

3 基于数据进行渲染


<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.00span>
    div>
  div>

  
  <div class="pay-way">
    <span class="tit">支付方式span>
    <div class="pay-cell">
      <span><van-icon name="balance-o" />余额支付(可用 ¥ {{ personal.balance }} 元)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>

(5) 订单结算 - 立即购买结算

1 点击跳转传参

prodetail/index.vue

<div class="btn" v-if="mode === 'buyNow'" @click="goBuyNow">立刻购买</div>

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

2 计算属性处理参数

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

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

async getOrderList () {
  ...
  
  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
  }
}

(6) mixins 复用 - 处理登录确认框的弹出

1 新建一个 mixin 文件 mixins/loginConfirm.js

mixin文件中可以编写Vue组件实例的配置项,通过一定语法,可以直接混入到组件内部data、methods、computed和生命周期函数

注意点:如果此处和组件内提供了同名的data或methods,则组件内优先级更高

export default{
 data(){
     return {
         title: '标题'
     }
 },
 methods(){
     sayHi(){}
 }
}

当其他组件引入mixin文件时,该组件中的data就会包含一个title属性,methods中会包含一个sayHi方法。

export default {
  methods: {
    // 是否需要弹登录确认框
    // (1) 需要,返回 true,并直接弹出登录确认框
    // (2) 不需要,返回 false
    loginConfirm () {
      if (!this.$store.getters.token) {
        this.$dialog.confirm({
          title: '温馨提示',
          message: '此时需要先登录才能继续操作哦',
          confirmButtonText: '去登陆',
          cancelButtonText: '再逛逛'
        })
          .then(() => {
            // 如果希望,跳转到登录 => 登录后能回跳回来,需要在跳转去携带参数 (当前的路径地址)
            // this.$route.fullPath (会包含查询参数)
            this.$router.replace({
              path: '/login',
              query: {
                backUrl: this.$route.fullPath
              }
            })
          })
          .catch(() => {})
        return true
      }
      return false
    }
  }
}

2 页面中导入,混入方法

import loginConfirm from '@/mixins/loginConfirm'

export default {
  name: 'ProDetail',
  mixins: [loginConfirm],
  ...
}

3 页面中调用 混入的方法

async addCart () {
  if (this.loginConfirm()) {
    return
  }
  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 () {
  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
    }
  })
}

02. 提交订单并支付

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

// 提交订单
export const submitOrder = (mode, params) => {
  return request.post('/checkout/submit', {
    mode,
    delivery: 10, // 物流方式  配送方式 (10快递配送 20门店自提)
    couponId: 0, // 优惠券 id
    payType: 10, // 余额支付
    isUsePoints: 0, // 是否使用积分
    ...params
  })
}

2 买家留言绑定

data () {
  return {
    remark: ''
  }
},

3 注册点击事件,提交订单并支付

提交订单
// 提交订单 async submitOrder () { if (this.mode === 'cart') { await submitOrder(this.mode, { remark: this.remark, cartIds: this.cartIds }) } 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') }

03. 订单管理

(1) 静态布局

1 基础静态结构






2 components/OrderListItem






3 导入注册

import { Tab, Tabs } from 'vant'
Vue.use(Tab)
Vue.use(Tabs)

(2) 点击 tab 切换渲染

1 封装获取订单列表的 API 接口

// 订单列表
export const getMyOrderList = (dataType, page) => {
  return request.get('/order/list', {
    params: {
      dataType,
      page
    }
  })
}

2 给 tab 绑定 name 属性


  
  
  
  
  


data () {
  return {
    active: this.$route.query.dataType || 'all',
    page: 1,
    list: []
  }
},

3 封装调用接口获取数据

methods: {
  async getOrderList () {
    const { data: { list } } = await getMyOrderList(this.active, this.page)
    list.data.forEach((item) => {
      item.total_num = 0
      item.goods.forEach(goods => {
        item.total_num += goods.total_num
      })
    })
    this.list = list.data
  }
},
watch: {
  active: {
    immediate: true,
    handler () {
      this.getOrderList()
    }
  }
}

4 动态渲染






你可能感兴趣的:(#,前端项目,javascript,vue.js)