vue3 之 商城项目—结算模块

路由配置

在这里插入图片描述
chekout/index.vue

<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" @click="toggleFlag = true">切换地址</el-button>
              <el-button size="large" @click="addFlag = true">添加地址</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>

router.js

// createRouter:创建router实例对象
// createWebHistory:创建history模式的路由
import { createRouter, createWebHistory } from 'vue-router'
import Login from '@/views/Login/index.vue'
import Layout from '@/views/Layout/index.vue'
import Home from '@/views/Home/index.vue'
import Category from '@/views/Category/index.vue'
import SubCategory from '@/views/SubCategory/index.vue'
import Detail from '@/views/Detail/index.vue'
import CartList from '@/views/CartList/index.vue'
import Checkout from '@/views/Checkout/index.vue'


const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  // path和component对应关系的位置
  routes: [
    {
      path: '/',
      component: Layout,
      children: [
        {
          path: '',
          component: Home
        },
        {
          path: 'category/:id',
          component: Category
        },
        {
          path: 'category/sub/:id',
          component: SubCategory
        },
        {
          path: 'detail/:id',
          component: Detail
        },
        {
          path: 'cartlist',
          component: CartList
        },
        {
          path: 'checkout',
          component: Checkout
        }
      ]
    },
    {
      path: '/login',
      component: Login
    }
  ],
  // 路由滚动行为定制
  scrollBehavior () {
    return {
      top: 0
    }
  }
})

export default router

cartList/index.vue

    <div class="total">
          <el-button size="large" type="primary" @click="$router.push('/checkout')">下单结算</el-button>
        </div>

基础数据渲染

在这里插入图片描述
apis/chekout.js

import request from '@/utils/request'
/**
 * 获取结算信息
 */
export const getCheckoutInfoAPI = () => {
  return request({
    url:'/member/order/pre'
  })
}

chekout/index.vue

<script setup>
import { getCheckInfoAPI, createOrderAPI } from '@/apis/checkout'
import { useRouter } from 'vue-router'
import { onMounted, ref } from 'vue'
import { useCartStore } from '@/stores/cartStore'
const cartStore = useCartStore()
const router = useRouter()
// 获取结算信息
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>

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

地址切换交互实现—打开弹窗

在这里插入图片描述
需求分析
1️⃣打开弹框交互:点击切换地址按钮,打开弹框,回显用户可选地址列表
2️⃣切换地址交互:点击切换地址,点击确定按钮,激活地址替换成默认收货地址
弹框模版

const showDialog = ref(false)
<el-dialog v-model="showDialog" 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>

添加按钮事件

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

地址切换交互实现—激活交互实现

原理
地址切换是我们经常遇到的tab切换类需求,这类需求的实现逻辑都是相似的
1️⃣点击时记录一个当前激活地址对象activeAddress,点击哪个地址就把哪个地址对象记录下来
2️⃣通过动态类名:class控制激活样式类型active是否存在,判断条件为:激活地址对象的id===当前项id

<script setup>
// 切换地址
const activeAddress = ref({})
const switchAddress = (item) => {
  activeAddress.value = item
}
const confirm = () => {
  curAddress.value = activeAddress.value
  showDialog.value = false
  activeAddress.value = {}
}
</script>
<template>
<div class="text item" 
  :class="{ active: activeAddress.id === item.id }" 
  @click="switchAddress(item)"
  :key="item.id">
</div>
</template>
 <!-- 切换地址 -->
  <el-dialog v-model="showDialog" title="切换收货地址" width="30%" center>
    <div class="addressWrapper">
      <div class="text item" :class="{ active: activeAddress.id === item.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>

生成订单功能实现

业务需求
确定结算信息没有问题之后,点击提交订单按钮,需要做以下两件事
1️⃣调用接口生成订单id,并且携带id跳转到支付页
2️⃣调用更新购物车列表接口,更新购物车状态
在这里插入图片描述

准备生成订单接口

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

调用接口携带id跳转路由

<script setup>
import { useCartStore } from '@/stores/cartStore'
const cartStore = useCartStore()
import { createOrderAPI } from '@/apis/checkout'
// 创建订单
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
    }
  })
    // 更新购物车
  cartStore.updateNewList()
}

</script>

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/390203.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

lenovo联想小新 Pro14 2022 Intel版IAH5R,AMD版ARH5R(82UT),(82UU)原装出厂Win11系统带恢复重置功能

联想小新Pro14笔记本电脑恢复原厂OEM预装Windows11系统镜像包&#xff0c;开箱状态时一模一样 Intel版(82UT)链接&#xff1a;https://pan.baidu.com/s/1jexQXkC6TerUnDQffwLooA?pwdcc09 提取码&#xff1a;cc09 AMD锐龙版(82UU)链接&#xff1a;https://pan.baidu.com/s/…

【FPGA开发】HDMI通信协议解析及FPGA实现

本篇文章包含的内容 一、HDMI简介1.1 HDMI引脚解析1.2 HDMI工作原理1.3 DVI编码1.4 TMDS编码 二、并串转换、单端差分转换原语2.1 原语简介2.2 IO端口组件 笔者在这里使用的开发板是正点原子的达芬奇开发板&#xff0c;FPGA型号为XC7A35TFGG484-2。参考的课程是正点原子的课程手…

Calendar的使用(Java)

直接从需求来理解&#xff1a;将2024年2月16日增加一个月 如果不使用Calendar的话&#xff0c;我们需要定义字符串记住这个日期&#xff0c;然后把字符串解析成Date日期对象&#xff0c;通过Date日期对象获取其毫秒值&#xff0c;然后增加一个月的毫秒值&#xff0c;再格式化时…

蓝桥杯第十四届电子类单片机组程序设计

目录 前言 蓝桥杯大赛历届真题&#xff08;点击查看&#xff09; 一、第十四届比赛题目 1.比赛原题 2.题目解读 1&#xff09;任务要求 2&#xff09;注意事项 二、任务实现 1.NE555读取时机的问题 1&#xff09;缩短计数时间 2&#xff09;实时读取 2.温度传感器读…

社区养老|社区养老服务系统|基于springboot社区养老服务系统设计与实现(源码+数据库+文档)

社区养老服务系统目录 目录 基于springboot社区养老服务系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员部分功能 &#xff08;1&#xff09; 用户管理 &#xff08;2&#xff09;服务种类管理 &#xff08;3&#xff09;社区服务管理 &#xff08…

MySQL 基础知识(八)之用户权限管理

目录 1 MySQL 权限管理概念 2 用户管理 2.1 创建用户 2.2 查看当前登录用户 2.3 修改用户名 2.4 删除用户 3 授予权限 3.1 授予用户管理员权限 3.2 授予用户数据库权限 3.3 授予用户表权限 3.4 授予用户列权限 4 查询权限 5 回收权限 1 MySQL 权限管理概念 关于 M…

NodeJS背后的人:Express

NodeJS背后的人&#xff1a;Express 本篇文章&#xff0c;学习记录于&#xff1a;尚硅谷&#x1f3a2; 文章简单学习总结&#xff1a;如有错误 大佬 &#x1f449;点. 前置知识&#xff1a;需要掌握了解&#xff1a; JavaScript基础语法 、Node.JS环境API 、前端工程\模块化 …

ESP32学习(3)——连接WIFI

1.简介 Wi-Fi是基于IEEE 802.11标准的无线网络技术 让联网设备以无线电波的形式&#xff0c;加入采用TCP/IP通信协议的网络. Wi-Fi设备有两种模式&#xff1a; 1.Access Point(AP) 模式&#xff0c;此为无线接入点&#xff0c;家里的光猫就是结合WiFi和internet路由功能的AP。…

Javaweb之SpringBootWeb案例之AOP通知类型的详细解析

3.1 通知类型 在入门程序当中&#xff0c;我们已经使用了一种功能最为强大的通知类型&#xff1a;Around环绕通知。 Around("execution(* com.itheima.service.*.*(..))") public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {//记录方法执行开始…

I/O并行口直接驱动LED显示

1&#xff0e;  实验任务 如图所示&#xff0c;利用AT89S51单片机的P0端口的P0.0&#xff0d;P0.7连接到一个共阴数码管的a&#xff0d;h的笔段上&#xff0c;数码管的公共端接地。在数码管上循环显示0&#xff0d;9数字&#xff0c;时间间隔0.2秒。 2&#xff0e;  电路原…

第7章 Page449~451 7.8.9智能指针 std::shared_ptr

“shared_ptr”是“共享式智能指针”。 即多个“shared_ptr”之间可以管理同一个裸指针。于是 O* o new O; //一个裸指针 std::shared_ptr <O> p1(o); //交给p1管 std::shared_ptr <O> p2(o); //又交给p2管 出乎意料&#xff0c;以上代码仍然是可以通过编译但运…

IO流---缓冲流,转换流,打印流,序列化流

缓冲流 缓冲流&#xff08;Buffered Stream&#xff09;也被称为高效流&#xff0c;它是对基本的字节字符流进行增强的一种流。通过缓冲流&#xff0c;可以提高数据的读写能力。 在创建缓冲流对象时&#xff0c;会创建一个内置的默认大小的缓冲区数组。通过对缓冲区的读写&…

JDBC教程+数据库连接池

JDBC 1.JDBC概述 ​ JDBC&#xff0c;全称Java数据库连接&#xff08;Java DataBase Connectivity&#xff09;&#xff0c;它是使用Java语言操作关系型数据库的一套API。 ​ JDBC本质是官方&#xff08;原SUN公司&#xff0c;现ORACLE&#xff09;定义的一套操作所有关系型数…

35岁转行,是我人生中最正确的选择

前言 经常听到有人说&#xff0c;35岁是职场的分水岭&#xff0c;但我觉得我的35岁&#xff0c;人生才刚刚开始。 35岁前后&#xff0c;我生二胎&#xff0c;考研&#xff0c;跳槽&#xff0c;转行&#xff0c;从传统行业到服务业&#xff0c;从服务业到新能源行业&#xff0…

静态时序分析:SDC约束命令set_clock_transition详解

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 在静态时序分析&#xff1a;SDC约束命令create_clock详解一文的最后&#xff0c;我们谈到了针对理想(ideal)时钟&#xff0c;可以使用set_clock_transition命令直…

函数、极限、连续——刷题(3

目录 1.题目&#xff1a;2.解题思路和步骤&#xff1a;3.总结&#xff1a;小结&#xff1a; 1.题目&#xff1a; 2.解题思路和步骤&#xff1a; 3.总结&#xff1a; 首先还是考虑好所有情况&#xff08;所有情况见&#xff1a;函数、极限、连续——刷题&#xff08;1&#xff…

JVM-垃圾回收(标记算法,收集器)

申明&#xff1a;文章内容是本人学习极客时间课程所写&#xff0c;文字和图片基本来源于课程资料&#xff0c;在某些地方会插入一点自己的理解&#xff0c;未用于商业用途&#xff0c;侵删。 原资料地址&#xff1a;课程资料 垃圾回收的基本原理 1 什么是垃圾&#xff1f; 在…

并发编程(1)基础篇

1 概览 1.1 这门课讲什么 这门课中的【并发】一词涵盖了在 Java 平台上的 进程线程并发并行 以及 Java 并发工具、并发问题以及解决方案&#xff0c;同时也会讲解一些其它领域的并发 1.2 为什么学这么课 我工作中用不到并发啊&#xff1f; 那你还是没有接触到复杂项目. …

JS如何判断普通函数与异步(async)函数

这里可以先打印一下普通函数和异步&#xff08;async&#xff09;函数的结构&#xff0c;如下图 可以看出两者原型链&#xff0c;普通函数的原型链指向的是一个函数&#xff0c;异步&#xff08;async&#xff09;函数原型链指向的是一个AsyncFunction&#xff0c;这时就会想到…

[高并发] - 1.高并发综述

1. 必备条件 高并发&#xff0c;高性能分布式ID 高并发过滤组件 Bloom FIlter 2. 数据库 &#xff08;1&#xff09;不要让mysql干不擅长的工作&#xff0c;例如全文搜索&#xff0c;而是采用对应的nosql来处理&#xff1b;对于擅长的存取数据则能很好胜任&#xff1b; &am…