Dark horse programmer front-end Vue3 Xiaotuxian e-commerce project - (10) order page


image-20230625225029159

Routing configuration and basic data rendering

template code

Create a new src\views\Checkout\index.vue file and add the following code:

<script setup>
const checkInfo = {}  // 订单对象
const curAddress = {}  // 地址对象

</script>

<template>
  <div class="xtx-pay-checkout-page">
    <div class="container">
      <div class="wrapper">
        <!-- 收货地址 -->
        <h3 class="box-title">收货地址</h3>
        <div class="box-body">
          <div class="address">
            <div class="text">
              <div class="none" v-if="!curAddress">您需要先添加收货地址才可提交订单。</div>
              <ul v-else>
                <li><span>收<i />货<i />人:</span>{
   
   { curAddress.receiver }}</li>
                <li><span>联系方式:</span>{
   
   { curAddress.contact }}</li>
                <li><span>收货地址:</span>{
   
   { curAddress.fullLocation }} {
   
   { curAddress.address }}</li>
              </ul>
            </div>
            <div class="action">
              <el-button size="large">切换地址</el-button>
              <el-button size="large">添加地址</el-button>
            </div>
          </div>
        </div>
        <!-- 商品信息 -->
        <h3 class="box-title">商品信息</h3>
        <div class="box-body">
          <table class="goods">
            <thead>
              <tr>
                <th width="520">商品信息</th>
                <th width="170">单价</th>
                <th width="170">数量</th>
                <th width="170">小计</th>
                <th width="170">实付</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="i in checkInfo.goods" :key="i.id">
                <td>
                  <a href="javascript:;" class="info">
                    <img :src="i.picture" alt="">
                    <div class="right">
                      <p>{
   
   { i.name }}</p>
                      <p>{
   
   { i.attrsText }}</p>
                    </div>
                  </a>
                </td>
                <td>&yen;{
   
   { i.price }}</td>
                <td>{
   
   { i.price }}</td>
                <td>&yen;{
   
   { i.totalPrice }}</td>
                <td>&yen;{
   
   { i.totalPayPrice }}</td>
              </tr>
            </tbody>
          </table>
        </div>
        <!-- 配送时间 -->
        <h3 class="box-title">配送时间</h3>
        <div class="box-body">
          <a class="my-btn active" href="javascript:;">不限送货时间:周一至周日</a>
          <a class="my-btn" href="javascript:;">工作日送货:周一至周五</a>
          <a class="my-btn" href="javascript:;">双休日、假日送货:周六至周日</a>
        </div>
        <!-- 支付方式 -->
        <h3 class="box-title">支付方式</h3>
        <div class="box-body">
          <a class="my-btn active" href="javascript:;">在线支付</a>
          <a class="my-btn" href="javascript:;">货到付款</a>
          <span style="color:#999">货到付款需付5元手续费</span>
        </div>
        <!-- 金额明细 -->
        <h3 class="box-title">金额明细</h3>
        <div class="box-body">
          <div class="total">
            <dl>
              <dt>商品件数:</dt>
              <dd>{
   
   { checkInfo.summary?.goodsCount }}件</dd>
            </dl>
            <dl>
              <dt>商品总价:</dt>
              <dd>¥{
   
   { checkInfo.summary?.totalPrice.toFixed(2) }}</dd>
            </dl>
            <dl>
              <dt>运<i></i>费:</dt>
              <dd>¥{
   
   { checkInfo.summary?.postFee.toFixed(2) }}</dd>
            </dl>
            <dl>
              <dt>应付总额:</dt>
              <dd class="price">{
   
   { checkInfo.summary?.totalPayPrice.toFixed(2) }}</dd>
            </dl>
          </div>
        </div>
        <!-- 提交订单 -->
        <div class="submit">
          <el-button type="primary" size="large" >提交订单</el-button>
        </div>
      </div>
    </div>
  </div>
  <!-- 切换地址 -->
  <!-- 添加地址 -->
</template>

<style scoped lang="scss">
.xtx-pay-checkout-page {
  margin-top: 20px;

  .wrapper {
    background: #fff;
    padding: 0 20px;

    .box-title {
      font-size: 16px;
      font-weight: normal;
      padding-left: 10px;
      line-height: 70px;
      border-bottom: 1px solid #f5f5f5;
    }

    .box-body {
      padding: 20px 0;
    }
  }
}

.address {
  border: 1px solid #f5f5f5;
  display: flex;
  align-items: center;

  .text {
    flex: 1;
    min-height: 90px;
    display: flex;
    align-items: center;

    .none {
      line-height: 90px;
      color: #999;
      text-align: center;
      width: 100%;
    }

    >ul {
      flex: 1;
      padding: 20px;

      li {
        line-height: 30px;

        span {
          color: #999;
          margin-right: 5px;

          >i {
            width: 0.5em;
            display: inline-block;
          }
        }
      }
    }

    >a {
      color: $xtxColor;
      width: 160px;
      text-align: center;
      height: 90px;
      line-height: 90px;
      border-right: 1px solid #f5f5f5;
    }
  }

  .action {
    width: 420px;
    text-align: center;

    .btn {
      width: 140px;
      height: 46px;
      line-height: 44px;
      font-size: 14px;

      &:first-child {
        margin-right: 10px;
      }
    }
  }
}

.goods {
  width: 100%;
  border-collapse: collapse;
  border-spacing: 0;

  .info {
    display: flex;
    text-align: left;

    img {
      width: 70px;
      height: 70px;
      margin-right: 20px;
    }

    .right {
      line-height: 24px;

      p {
        &:last-child {
          color: #999;
        }
      }
    }
  }

  tr {
    th {
      background: #f5f5f5;
      font-weight: normal;
    }

    td,
    th {
      text-align: center;
      padding: 20px;
      border-bottom: 1px solid #f5f5f5;

      &:first-child {
        border-left: 1px solid #f5f5f5;
      }

      &:last-child {
        border-right: 1px solid #f5f5f5;
      }
    }
  }
}

.my-btn {
  width: 228px;
  height: 50px;
  border: 1px solid #e4e4e4;
  text-align: center;
  line-height: 48px;
  margin-right: 25px;
  color: #666666;
  display: inline-block;

  &.active,
  &:hover {
    border-color: $xtxColor;
  }
}

.total {
  dl {
    display: flex;
    justify-content: flex-end;
    line-height: 50px;

    dt {
      i {
        display: inline-block;
        width: 2em;
      }
    }

    dd {
      width: 240px;
      text-align: right;
      padding-right: 70px;

      &.price {
        font-size: 20px;
        color: $priceColor;
      }
    }
  }
}

.submit {
  text-align: right;
  padding: 60px;
  border-top: 1px solid #f5f5f5;
}

.addressWrapper {
  max-height: 500px;
  overflow-y: auto;
}

.text {
  flex: 1;
  min-height: 90px;
  display: flex;
  align-items: center;

  &.item {
    border: 1px solid #f5f5f5;
    margin-bottom: 10px;
    cursor: pointer;

    &.active,
    &:hover {
      border-color: $xtxColor;
      background: lighten($xtxColor, 50%);
    }

    >ul {
      padding: 10px;
      font-size: 14px;
      line-height: 30px;
    }
  }
}
</style>

configure routing

Configure routing in src\router\index.js:

routes: [
  {
    
    
    path: '/',
    component: Layout,
    children: [
      ...
      {
    
    
        path: "checkout",
        component: Checkout
      }
    ]
  }
]

package interface

Create a new src\apis\checkout.js file and add an interface for obtaining order details:

import http from '@/utils/request'

// 获取详情接口
export const getCheckInfoAPI = () => {
    
    
    return http({
    
    
        url: '/member/order/pre'
    })
}

render data

In the src\views\Checkout\index.vue file, call the interface to obtain order data, and traverse the address:

<script setup>
import {
    
     getCheckInfoAPI } from '@/apis/checkout';
import {
    
     ref, onMounted } from 'vue'

const checkInfo = ref({
    
    })  // 订单对象
const curAddress = ref({
    
    })  // 地址对象

const getCheckInfo = async () => {
    
    
    const res = await getCheckInfoAPI()
    checkInfo.value = res.result
    //适配默认地址
    //从地址列表中筛选出来 isDefault === 0 那一项
    const item = checkInfo.value.userAddresses.find(item => item.isDefault === 0)
    curAddress.value = item
}
onMounted(() => {
    
    
    getCheckInfo()
})

</script>

Switch address - open pop-up box interaction

Add the code of the pop-up window at the bottom of src\views\Checkout\index.vue:

<!-- 切换地址 -->
<el-dialog title="切换收货地址" width="30%" center>
  <div class="addressWrapper">
    <div class="text item" v-for="item in checkInfo.userAddresses" :key="item.id">
      <ul>
        <li><span><i /><i />人:</span>{
   
   { item.receiver }} </li>
        <li><span>联系方式:</span>{
   
   { item.contact }}</li>
        <li><span>收货地址:</span>{
   
   { item.fullLocation + item.address }}</li>
      </ul>
    </div>
  </div>
  <template #footer>
    <span class="dialog-footer">
      <el-button>取消</el-button>
      <el-button type="primary">确定</el-button>
    </span>
  </template>
</el-dialog>
<!-- 添加地址 -->

Define variables to control pop-up window display:

//控制弹窗打开
const showDialog = ref(false)

Pop-up binding data:

<el-dialog v-model="showDialog" title="切换收货地址" width="30%" center>
  ...
</el-dialog>

[Switch address] button binding event:

<div class="action">
  <el-button size="large" @click="showDialog = true">切换地址</el-button>
  <el-button size="large">添加地址</el-button>
</div>

switch address - address switch interaction

Basic idea: record the current click item, and judge whether the current div has an active class name through the dynamic class

Write a method in src\views\Checkout\index.vue to record address information:

//切换地址
const activeAddress = ref({
    
    })
const switchAddres  = (item)=>{
    
    
    activeAddress.value=item
}
//覆盖地址
const confirm =()=>{
    
    
    curAddress.value = activeAddress.value
    showDialog.value=false
}

Bind switch address, activate address event:

<!-- 切换地址 -->
<el-dialog v-model="showDialog" title="切换收货地址" width="30%" center>
  <div class="addressWrapper">
    <div class="text item" :class="active:item.id===activeAddress.id" @click="switchAddress(item)" v-for="item in checkInfo.userAddresses" :key="item.id">
      <ul>
        <li><span><i /><i />人:</span>{
   
   { item.receiver }} </li>
        <li><span>联系方式:</span>{
   
   { item.contact }}</li>
        <li><span>收货地址:</span>{
   
   { item.fullLocation + item.address }}</li>
      </ul>
    </div>
  </div>
  <template #footer>
    <span class="dialog-footer">
      <el-button>取消</el-button>
      <el-button type="primary" @click="confirm">确定</el-button>
    </span>
  </template>
</el-dialog>

Generate orders

After confirming that there is no problem with the settlement information, click the submit order button, and you need to do the following two things:

  1. Call the interface to generate the order id, and jump to the payment page with the id
  2. Call the update shopping cart list interface to update the shopping cart status

Payment Page Components

src\views\Pay\index.vue file, the code is as follows:

<script setup>
const payInfo = {}
</script>

<template>
  <div class="xtx-pay-page">
    <div class="container">
      <!-- 付款信息 -->
      <div class="pay-info">
        <span class="icon iconfont icon-queren2"></span>
        <div class="tip">
          <p>订单提交成功!请尽快完成支付。</p>
          <p>支付还剩 <span>24分30秒</span>, 超时后将取消订单</p>
        </div>
        <div class="amount">
          <span>应付总额:</span>
          <span>¥{
   
   { payInfo.payMoney?.toFixed(2) }}</span>
        </div>
      </div>
      <!-- 付款方式 -->
      <div class="pay-type">
        <p class="head">选择以下支付方式付款</p>
        <div class="item">
          <p>支付平台</p>
          <a class="btn wx" href="javascript:;"></a>
          <a class="btn alipay" :href="payUrl"></a>
        </div>
        <div class="item">
          <p>支付方式</p>
          <a class="btn" href="javascript:;">招商银行</a>
          <a class="btn" href="javascript:;">工商银行</a>
          <a class="btn" href="javascript:;">建设银行</a>
          <a class="btn" href="javascript:;">农业银行</a>
          <a class="btn" href="javascript:;">交通银行</a>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped lang="scss">
.xtx-pay-page {
  margin-top: 20px;
}

.pay-info {

  background: #fff;
  display: flex;
  align-items: center;
  height: 240px;
  padding: 0 80px;

  .icon {
    font-size: 80px;
    color: #1dc779;
  }

  .tip {
    padding-left: 10px;
    flex: 1;

    p {
      &:first-child {
        font-size: 20px;
        margin-bottom: 5px;
      }

      &:last-child {
        color: #999;
        font-size: 16px;
      }
    }
  }

  .amount {
    span {
      &:first-child {
        font-size: 16px;
        color: #999;
      }

      &:last-child {
        color: $priceColor;
        font-size: 20px;
      }
    }
  }
}

.pay-type {
  margin-top: 20px;
  background-color: #fff;
  padding-bottom: 70px;

  p {
    line-height: 70px;
    height: 70px;
    padding-left: 30px;
    font-size: 16px;

    &.head {
      border-bottom: 1px solid #f5f5f5;
    }
  }

  .btn {
    width: 150px;
    height: 50px;
    border: 1px solid #e4e4e4;
    text-align: center;
    line-height: 48px;
    margin-left: 30px;
    color: #666666;
    display: inline-block;

    &.active,
    &:hover {
      border-color: $xtxColor;
    }

    &.alipay {
      background: url(https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7b6b02396368c9314528c0bbd85a2e06.png) no-repeat center / contain;
    }

    &.wx {
      background: url(https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/c66f98cff8649bd5ba722c2e8067c6ca.jpg) no-repeat center / contain;
    }
  }
}
</style>

package order interface

src\apis\checkout.js encapsulates the interface for creating orders:

// 创建订单
export const createOrderAPI = (data) => {
    
    
  return http({
    
    
    url: '/member/order',
    method: 'POST',
    data
  })
}

bind event

Click the [Submit Order] button to create an order and clear the shopping cart:

<script setup>
  import { getCheckInfoAPI,createOrderAPI } from '@/apis/checkout'
  import { ref, onMounted } from 'vue'
  import { useRouter } from 'vue-router'
  import { useCartStore } from '@/stores/cartStore'

  const cartStore = useCartStore()
  const router = useRouter()

  // 创建订单
  const createOrder = async () => {
    const res = await createOrderAPI({
      deliveryTimeType: 1,
      payType: 1,
      payChannel: 1,
      buyerMessage: '',
      goods: checkInfo.value.goods.map(item => {
        return {
          skuId: item.skuId,
          count: item.count
        }
      }),
      addressId: curAddress.value.id
    })
    const orderId = res.result.id
    router.push({
      path: '/pay',
      query: {
        id: orderId
      }
    })
  }

</script>

<template>
<!-- 提交订单 -->
<div class="submit">
  <el-button @click="createOrder" type="primary" size="large">提交订单</el-button>
  </div>
</template>

Guess you like

Origin blog.csdn.net/qq_20185737/article/details/131387563