Vue2项目之移动端购物商城(五) 结算页的实现,订单管理页的实现,个人中心的实现,项目打包优化,mixin封装弹出确认框,vuex中的跨模块访问

一.结算页的实现

所有的结算本质上都会跳转到订单结算页Pay/index.vue,跳转的同时携带对应的订单相应参数
具体需要哪些参数,基于pay页的需求来定的

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">13811112222</span>
        </div>
        <div class="info-address">
          江苏省 无锡市 南长街 110504
        </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/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.【获取收货地址】查看文档,封装接口,页面调用,动态渲染
  • 查看文档
    在这里插入图片描述
  • 封装接口
//新建api/address.js
import request from "@/utils/request"
//获取地址列表
export const getAddressList=()=>{
    return request.get('/address/list')
}
  • 页面调用
import { getAddressList } from '@/api/address'
export default {
  name: 'PayIndex',
  data () {
    return {
      addressList: []
    }
  },
  computed: {
    selectedAddress () {
    // 由于地址管理非该项目的主线业务,直接获取第一个项作为选中的地址
      return this.addressList[0] || {}
    }
  },
  created () {
    this.getAddressList()
  },
  methods: {
    async getAddressList () {
      const { data: { list } } = await getAddressList()// 调用
      this.addressList = list
      // console.log('获取收货地址:', list)// 取里面的第一项作为值渲染==>可以提供一个计算属性selectedAddress
    }
  }
}
  • 动态渲染
    <!-- 地址相关 -->
    <div class="address">
		......
      <div class="info" v-if="selectedAddress.address_id">
        <div class="info-content">
          <span class="name">{{selectedAddress.name}}</span>
          <span class="mobile">{{selectedAddress.phone}}</span>
        </div>
        <!-- 用计算属性拼接省市街道:江苏省 无锡市 南长街 110504 -->
        <div class="info-address">
          {{longAddress}}
        </div>
      </div>
		......
    </div>

  computed: {
    selectedAddress () {
    // 由于地址管理非该项目的主线业务,直接获取第一个项作为选中的地址
      return this.addressList[0] || {}
    },
    longAddress () {
      const region = this.selectedAddress.region
      return region.province + ' ' + region.city + ' ' + region.region//手动拼接地址
    }
  }
3.【获取订单信息-购物车结算】查看文档,封装接口,路由传参,页面调用,动态渲染
  • 查看文档
    在这里插入图片描述

订单信息的确认分为购物车结算(cart页=>pay页)和立即购买结算(productDetail页=>pay页),从请求参数mode的值来确认
     购物车结算时,需要带上的请求参数是:mode:cart,cartsId
     立即购买结算时,需要带上的请求参数是:mode:buyNow,goodsId,goodsNum,goodsSkuId

  • 封装接口:(两种情况通用)
import request from '@/utils/request'

export const checkOrder = (mode, obj) => {
  return request.get('/checkout.order', {
    params: {
      mode,
      ...obj, // 如何根据不同的mode值,传递不同的参数对象:动态展开
      /* 非主线业务:给默认值 */
      delivery: 10, // 10代表快速配送,20代表门店自提
      couponId: 0, // 优惠券id,传参,默认不使用
      isUsePoints: 0// 使用积分,默认不使用
    }
  })
}
  • Cart页配置查询参数传参并路由跳转到pay
//cart.vue:传参
//绑定点击事件
<div v-if="!isEdit" @click="goPay" class="goPay" :disabled="selCount === 0">结算({{ selCount}})</div>
//定义点击事件
    // 跳转结算页
    goPay () {
      // 先判断有没有选中商品(getters属性里封装的selCount或selCountList)
      if (this.selCount > 0) {
        // 查询参数传参(完整写法)
        this.$router.push({
          path: 'pay',
          query: {
            mode: 'cart',
            // 得到一个当前选中的商品的id组成的新数组,再用join拼接成字符串就是我们想要的格式:70642.70643.70644.70661.70664
            cartIds: this.selCartList.map(item => item.id).join('.')
          }
        })
      }
    }


//Pay/index.js:接收参数
computed:{
    mode () { return this.$route.query.mode },
    cartIds () { return this.$route.query.cartIds }
},
created () {
    console.log('pay页获得的mode和cartIds:', this.cartIds, this.mode)//70661.70664.70666.70667 cart
}
  • Pay页调用接口获取数据:用cart页传过来的参数作为调用时的传参参数
import { checkOrder } from "@/api/order.js"
.....
data(){
	return{
		order:{},
		personal:{}
	}
},
created(){
	this.checkOrderMethods()
},
methods:{
	//获取订单列表
	async checkOrderMethods(){
		const { data :{ order, personal }}=checkOrder(this.mode,{cartIds: this.cartIds})
		//console.log(res)
		this.order=order
		this.personal=personal
	}
}
  • 动态渲染:用获取到的orderpersonal渲染页面数据
<!-- 订单明细 -->
<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>

4.【获取订单信息-立即购买结算】路由传参,页面调用,动态渲染,确认框封装到mixin
  • 详情页踩坑纠正:由于要验证token,误将"加入购物车"和"立即购买"绑在了同一个点击事件handlePannel中,现纠正如下
/******************之前的错误代码*****************/
<!-- ActionSheet弹层 -->
<van-action-sheet @click='handlePannel' v-model="showPannel" :title="mode==='cart'?'加入购物车':'立即购买'">
  <!-- v-if有库存 -->
  <div class="showbtn" v-if="detail.stock_total>0">
    <div class="btn" v-if="mode==='cart'">加入购物车</div>
    <div class="btn" v-else>立即购买</div>
  </div>
</van-action-sheet>
//在handlePannel事件中验证token
    // 点击弹层中的按钮:"加入购物车"或"立即购买"
    handlePannel () {
      // 验证token是否存在
      // console.log('我是token', this.userInfo.token)

      if (!this.userInfo.token) { // 不存在:弹出提示框
        // Dialog确认框
        this.$dialog.confirm({
          title: '温馨提示',
          message: '此时需要先登录才能继续操作哦',
          confirmButtomText: '去登录',
          cancelButtonText: '再逛逛'
        }).then(() => {
          this.$router.replace({
            path: '/login',
            query: {
              backUrl: this.$route.fullPath
            }
          })
        }).catch(() => {})
      }
      this.addCartMethods()
      this.$toast(this.msg)
      this.showPannel = false
    }

/******************现在的纠正代码*****************/
<!-- ActionSheet弹层 -->
<van-action-sheet v-model="showPannel" :title="mode==='cart'?'加入购物车':'立即购买'">
  <!-- v-if有库存 -->
  <div class="showbtn" v-if="detail.stock_total>0">
    <div class="btn" v-if="mode==='cart'" @click='handleAddCart'>加入购物车</div>
    <div class="btn" v-else @click='handleBuyNow'>立即购买</div>
  </div>
</van-action-sheet>

    // 点击弹层中的按钮:"加入购物车"
    handleAddCart () {
      // 验证token是否存在
      // console.log('我是token', this.userInfo.token)

      if (!this.userInfo.token) { // 不存在:弹出提示框
        // Dialog确认框
        this.$dialog.confirm({
          title: '温馨提示',
          message: '此时需要先登录才能继续操作哦',
          confirmButtomText: '去登录',
          cancelButtonText: '再逛逛'
        }).then(() => {
          this.$router.replace({
            path: '/login',
            query: {
              backUrl: this.$route.fullPath
            }
          })
        }).catch(() => {})
      }
      this.addCartMethods()
      this.$toast(this.msg)
      this.showPannel = false
    },
    // 点击弹层中的按钮:"立即购买"
    handleBuyNow(){
		....后面写...
	}
  • ProductDetail页配置查询参数传参并路由跳转到pay
    // 点击弹层中的按钮:"立即购买"
    /****待优化:可以把两个函数共同有的确认框验证token部分提取封装****/
    handleBuyNow () {
      if (!this.userInfo.token) { // 不存在:弹出提示框
        // Dialog确认框
        this.$dialog.confirm({
          title: '温馨提示',
          message: '此时需要先登录才能继续操作哦',
          confirmButtomText: '去登录',
          cancelButtonText: '再逛逛'
        }).then(() => {
          this.$router.replace({
            path: '/login',
            query: {
              backUrl: this.$route.fullPath
            }
          })
        }).catch(() => {})
      }
      this.$router.push({
        path: '/pay',
        query: {
          mode: 'buyNow',
          goodsId: this.goodsId,
          goodsSkuId: this.detail.skuList[0].goods_sku_id,
          goodsNum: this.addCount
        }
      })
    },
  • Pay页调用接口获取数据:用ProductDetail页传过来的参数作为调用时的传参参数
    此时pay页已经渲染了从购物车结算的情况,需对pay页进行修改如下
computed:{
   goodsId(){return this.$route.query.goodsId},
   goodsNum(){return this.$route.query.goodsNum},
   goodsSkuId(){return this.$route.query.goodsSkuId},
}
//methods:
    // 获取订单信息
    async getOrderList () {
      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, goodsNum: this.goodsNum, goodsSkuId: this.goodsSkuId })
        this.order = order
        this.personal = personal
      }
    }
  • 优化:把详情页中"加入购物车"和"立即购买"的确认框封装起来

使用到了mixin混入
step1:新建src/mixins/loginConfirm.js,并把确认登录的代码剪切到该文件中

//src/mixins/loginConfirm.js
export default {
  methods: {
    loginConfirm () { // 登录确认框
      if (!this.userInfo.token) { // token不存在==>弹确认框
        // 页面中使用Dialog框
        this.$dialog.confirm({
          title: '温馨提示',
          message: '此时需要先登录才能继续操作哦',
          confirmButtonText: '去登录',
          cancelButtonText: '再逛逛'
        })
          .then(() => {
            this.$router.push({
              path: '/login',
              query: {
                backUrl: this.$router.fullPath
              }
            })
          })
          .catch = () => {}
        return true// 弹出了
      }
      return false// 没弹出
    }
  }
}

step2:导入并在剪切的原位置使用mixiin中封装的方法

//ProductDetail/index.vue
import loginConfirm from '@/mixins/loginConfirm'
export default {
  mixin: [loginConfirm], // 里面不要加引号!
  methods:{
		hendleAddCart(){
			if(this.loginConfirm()) true
			...
		},
		hendleBuyNow(){
			if(this.loginConfirm()) true
			...
		}
	}
}
5.【提交订单并支付-两种结算】查看文档,封装请求,页面注册事件并调用请求
  • 查看文档,确认请求方式,请求路径,请求参数
    在这里插入图片描述
    参数同上(都是分两种情况)
  • 封装接口
//api/order.js
// 提交订单
export const submitOrder = (mode, obj) => {
  return request.post('/checkout/submit', {
    mode,
    ...obj,
    /* 非主线业务, */
    delivery: 10, // 10代表快速配送,20代表门店自提
    couponId: 0, // 优惠券id,传参,默认不使用
    isUsePoints: 0, // 使用积分,默认不使用
    payType: 10// 余额支付
  })
}

. 给页面中的提交订单按钮绑定点击事件,并调用接口
(再加上留言属性remark)

//Pay/index.vue
  <!-- 买家留言 -->
  <div class="buytips">
    <textarea v-model="remark" 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" @click='submitOrder'>提交订单</div>
</div>
......
import { checkOrder, submitOrder } from '@/api/order'
......
data:
	remark:''
methods:
    // 提交订单
    async submitOrder () {
      if (this.mode === 'cart') { // 购物车结算
        await submitOrder(this.mode, {
          cartIds: this.cartIds,
          remark: this.remark
        })
      }
      if (this.mode === 'buyNow') { // 立刻购买结算
        await submitOrder(this.mode, {
          goodsId: this.goodsId,
          goodsNum: this.goodsNum,
          goodsSkuId: this.goodsSkuId,
          remark: this.remark
        })
      }
      
      this.$toast.success('支付成功')
      this.$router.replace('/myorder')
    }

二.订单管理页的实现

前置要求:

  • utils/vant-ui,js按需引入Tabs和Tab组件
  • 创建components/OrderListItem.vue子组件
1.静态布局
MyOrder/index.vue
<template>
  <div class="order">
    <van-nav-bar title="我的订单" left-arrow @click-left="$router.go(-1)" />
    <van-tabs v-model="active">
      <van-tab title="全部"></van-tab>
      <van-tab title="待支付"></van-tab>
      <van-tab title="待发货"></van-tab>
      <van-tab title="待收货"></van-tab>
      <van-tab title="待评价"></van-tab>
    </van-tabs>

    <OrderListItem></OrderListItem>
  </div>
</template>

<script>
import OrderListItem from '@/components/OrderListItem.vue'
export default {
  name: 'OrderPage',
  components: {
    OrderListItem
  },
  data () {
    return {
      active: 0
    }
  }
}
</script>

<style lang="less" scoped>
.order {
  background-color: #fafafa;
}
.van-tabs {
  position: sticky;
  top: 0;
}
</style>

components/OrderListItem.vue
<template>
  <div class="order-list-item">
    <div class="tit">
      <div class="time">2023-07-01 12:02:13</div>
      <div class="status">
        <span>待支付</span>
      </div>
    </div>
    <div class="list">
      <div class="list-item">
        <div class="goods-img">
          <img src="http://cba.itlike.com/public/uploads/10001/20230321/c4b5c61e46489bb9b9c0630002fbd69e.jpg" alt="">
        </div>
        <div class="goods-content text-ellipsis-2">
          Apple iPhone 14 Pro Max 256G 银色 移动联通电信5G双卡双待手机
        </div>
        <div class="goods-trade">
          <p>¥ 1299.00</p>
          <p>x 3</p>
        </div>
      </div>
      <div class="list-item">
        <div class="goods-img">
          <img src="http://cba.itlike.com/public/uploads/10001/20230321/c4b5c61e46489bb9b9c0630002fbd69e.jpg" alt="">
        </div>
        <div class="goods-content text-ellipsis-2">
          Apple iPhone 14 Pro Max 256G 银色 移动联通电信5G双卡双待手机
        </div>
        <div class="goods-trade">
          <p>¥ 1299.00</p>
          <p>x 3</p>
        </div>
      </div>
      <div class="list-item">
        <div class="goods-img">
          <img src="http://cba.itlike.com/public/uploads/10001/20230321/c4b5c61e46489bb9b9c0630002fbd69e.jpg" alt="">
        </div>
        <div class="goods-content text-ellipsis-2">
          Apple iPhone 14 Pro Max 256G 银色 移动联通电信5G双卡双待手机
        </div>
        <div class="goods-trade">
          <p>¥ 1299.00</p>
          <p>x 3</p>
        </div>
      </div>
    </div>
    <div class="total">12件商品,总金额 ¥29888.00
    </div>
    <div class="actions">
      <span v-if="false">立刻付款</span>
      <span v-if="true">申请取消</span>
      <span v-if="false">确认收货</span>
      <span v-if="false">评价</span>
    </div>
  </div>
</template>

<script>
export default {

}
</script>

<style lang="less" scoped>
.order-list-item {
  margin: 10px auto;
  width: 94%;
  padding: 15px;
  background-color: #ffffff;
  box-shadow: 0 0.5px 2px 0 rgba(0,0,0,.05);
  border-radius: 8px;
  color: #333;
  font-size: 13px;

  .tit {
    height: 24px;
    line-height: 24px;
    display: flex;
    justify-content: space-between;
    margin-bottom: 20px;
    .status {
      color: #fa2209;
    }
  }

  .list-item {
    display: flex;
    .goods-img {
      width: 90px;
      height: 90px;
      margin: 0px 10px 10px 0;
      img {
        width: 100%;
        height: 100%;
      }
    }
    .goods-content {
      flex: 2;
      line-height: 18px;
      max-height: 36px;
      margin-top: 8px;
    }
    .goods-trade {
      flex: 1;
      line-height: 18px;
      text-align: right;
      color: #b39999;
      margin-top: 8px;
    }
  }

  .total {
    text-align: right;
  }
  .actions {
    text-align: right;
    span {
      display: inline-block;
      height: 28px;
      line-height: 28px;
      color: #383838;
      border: 0.5px solid #a8a8a8;
      font-size: 14px;
      padding: 0 15px;
      border-radius: 5px;
      margin: 10px 0;
    }
  }
}
</style>

在这里插入图片描述

2.查看文档并封装接口

在这里插入图片描述

//api/order.js
// 【myOrder页】获取订单列表
export const getMyOrderList = (dataType, page) => {
  return request.get('/order/list', {
    params: {
      dataType,
      page
    }
  })
}
3.给tab添加name属性,页面调用接口
<template>
  <div class="order">
    <van-nav-bar title="我的订单" left-arrow @click-left="$router.go(-1)" />
   <!-- sticky:使用粘性定位布局 -->
    <van-tabs v-model="active" sticky>
      <van-tab name='all' title="全部"></van-tab>
      <van-tab name='payment' title="待支付"></van-tab>
      <van-tab name='delivery' title="待发货"></van-tab>
      <van-tab name='received' title="待收货"></van-tab>
      <van-tab name='comment' title="待评价"></van-tab>
    </van-tabs>

    <OrderListItem></OrderListItem>
  </div>
</template>

<script>
import OrderListItem from '@/components/OrderListItem.vue'
import { getMyOrderList } from '@/api/order'
export default {
  name: 'OrderPage',
  components: {
    OrderListItem
  },
  data () {
    return {
      active: this.$route.query.dataType || 'all',
      page: 1,
      list: []
    }
  },
  methods: {
    async getMyOrderListMethods () {
      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.getMyOrderListMethods()
      }
    }
  }
}
</script>

4.动态渲染:父传子并渲染OrderListItem子组件
父:MyOrder页
<OrderListItem v-for="item in list" :key="item.order_id" :item="item"></OrderListItem>
子:OrderListItem页
<template>
  <div class="order-list-item" v-if="item.order_id">
    <div class="tit">
      <div class="time">{{ item.create_time }}</div>
      <div class="status">
        <span>{{ item.state_text }}</span>
      </div>
    </div>
    <div class="list" >
      <div class="list-item" v-for="(goods, index) in item.goods" :key="index">
        <div class="goods-img">
          <img :src="goods.goods_image" alt="">
        </div>
        <div class="goods-content text-ellipsis-2">
          {{ goods.goods_name }}
        </div>
        <div class="goods-trade">
          <p>¥ {{ goods.total_pay_price }}</p>
          <p>x {{ goods.total_num }}</p>
        </div>
      </div>
    </div>
    <div class="total">
      共 {{ item.total_num }} 件商品,总金额 ¥{{ item.total_price }}
    </div>
    <div class="actions">
      <div v-if="item.order_status === 10">
        <span v-if="item.pay_status === 10">立刻付款</span>
        <span v-else-if="item.delivery_status === 10">申请取消</span>
        <span v-else-if="item.delivery_status === 20 || item.delivery_status === 30">确认收货</span>
      </div>
      <div v-if="item.order_status === 30">
        <span>评价</span>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    item: {
      type: Object,
      default: () => {
        return {}
      }
    }
  }
}
</script>

在这里插入图片描述

三.个人中心页的实现

个人中心页是嵌套在Layout下的二级路由User.vue

1.查看文档,封装接口

在这里插入图片描述

//api/user.js
import request from '@/utils/request'
// 获取个人信息
export const getUserInfoDetail = () => {
  request.get('/user/info')
}
2.页面调用,动态渲染

User.vue完整代码(此处如没有avatar.png本地图片,去网页上找默认用户头像,复制在线URL即可)

<template>
  <div class="user">
    <div class="head-page" v-if="isLogin">
      <div class="head-img">
        <img src="https://p1.ssl.qhimgs1.com/sdr/400__/t01d81c9f80f26024b7.jpg" alt="" />
      </div>
      <div class="info">
        <div class="mobile">{{ detail.mobile }}</div>
        <div class="vip">
          <van-icon name="diamond-o" />
          普通会员
        </div>
      </div>
    </div>

    <div v-else class="head-page" @click="$router.push('/login')">
      <div class="head-img">
        <img src="https://p1.ssl.qhimgs1.com/sdr/400__/t01d81c9f80f26024b7.jpg" alt="" />
      </div>
      <div class="info">
        <div class="mobile">未登录</div>
        <div class="words">点击登录账号</div>
      </div>
    </div>

    <div class="my-asset">
      <div class="asset-left">
        <div class="asset-left-item">
          <span>{{ detail.pay_money || 0 }}</span>
          <span>账户余额</span>
        </div>
        <div class="asset-left-item">
          <span>0</span>
          <span>积分</span>
        </div>
        <div class="asset-left-item">
          <span>0</span>
          <span>优惠券</span>
        </div>
      </div>
      <div class="asset-right">
        <div class="asset-right-item">
          <van-icon name="balance-pay" />
          <span>我的钱包</span>
        </div>
      </div>
    </div>
    <div class="order-navbar">
      <div class="order-navbar-item" @click="$router.push('/myorder?dataType=all')">
        <van-icon name="balance-list-o" />
        <span>全部订单</span>
      </div>
      <div class="order-navbar-item" @click="$router.push('/myorder?dataType=payment')">
        <van-icon name="clock-o" />
        <span>待支付</span>
      </div>
      <div class="order-navbar-item" @click="$router.push('/myorder?dataType=delivery')">
        <van-icon name="logistics" />
        <span>待发货</span>
      </div>
      <div class="order-navbar-item" @click="$router.push('/myorder?dataType=received')">
        <van-icon name="send-gift-o" />
        <span>待收货</span>
      </div>
    </div>

    <div class="service">
      <div class="title">我的服务</div>
      <div class="content">
        <div class="content-item">
          <van-icon name="records" />
          <span>收货地址</span>
        </div>
        <div class="content-item">
          <van-icon name="gift-o" />
          <span>领券中心</span>
        </div>
        <div class="content-item">
          <van-icon name="gift-card-o" />
          <span>优惠券</span>
        </div>
        <div class="content-item">
          <van-icon name="question-o" />
          <span>我的帮助</span>
        </div>
        <div class="content-item">
          <van-icon name="balance-o" />
          <span>我的积分</span>
        </div>
        <div class="content-item">
          <van-icon name="refund-o" />
          <span>退换/售后</span>
        </div>
      </div>
    </div>

    <div class="logout-btn">
     <button>退出登录</button>
    </div>
  </div>
</template>

<script>
import { getUserInfoDetail } from '@/api/user.js'
export default {
  name: 'UserPage',
  data () {
    return {
      detail: {}
    }
  },
  created () {
    if (this.isLogin) {
      this.getUserInfoDetail()
    }
  },
  computed: {
    isLogin () {
      return this.$store.getters.token
    }
  },
  methods: {
    async getUserInfoDetail () {
      const { data: { userInfo } } = await getUserInfoDetail()
      this.detail = userInfo
      console.log(this.detail)
    }
  }
}
</script>

<style lang="less" scoped>
.user {
  min-height: 100vh;
  background-color: #f7f7f7;
  padding-bottom: 50px;
}

.head-page {
  height: 130px;
  background: url("http://cba.itlike.com/public/mweb/static/background/user-header2.png");
  background-size: cover;
  display: flex;
  align-items: center;
  .head-img {
    width: 50px;
    height: 50px;
    border-radius: 50%;
    overflow: hidden;
    margin: 0 10px;
    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
  }
}
.info {
  .mobile {
    margin-bottom: 5px;
    color: #c59a46;
    font-size: 18px;
    font-weight: bold;
  }
  .vip {
    display: inline-block;
    background-color: #3c3c3c;
    padding: 3px 5px;
    border-radius: 5px;
    color: #e0d3b6;
    font-size: 14px;
    .van-icon {
      font-weight: bold;
      color: #ffb632;
    }
  }
}

.my-asset {
  display: flex;
  padding: 20px 0;
  font-size: 14px;
  background-color: #fff;
  .asset-left {
    display: flex;
    justify-content: space-evenly;
    flex: 3;
    .asset-left-item {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      span:first-child {
        margin-bottom: 5px;
        color: #ff0000;
        font-size: 16px;
      }
    }
  }
  .asset-right {
    flex: 1;
    .asset-right-item {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      .van-icon {
        font-size: 24px;
        margin-bottom: 5px;
      }
    }
  }
}

.order-navbar {
  display: flex;
  padding: 15px 0;
  margin: 10px;
  font-size: 14px;
  background-color: #fff;
  border-radius: 5px;
  .order-navbar-item {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 25%;
    .van-icon {
      font-size: 24px;
      margin-bottom: 5px;
    }
  }
}

.service {
  font-size: 14px;
  background-color: #fff;
  border-radius: 5px;
  margin: 10px;
  .title {
    height: 50px;
    line-height: 50px;
    padding: 0 15px;
    font-size: 16px;
  }
  .content {
    display: flex;
    justify-content: flex-start;
    flex-wrap: wrap;
    font-size: 14px;
    background-color: #fff;
    border-radius: 5px;
    .content-item {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      width: 25%;
      margin-bottom: 20px;

      .van-icon {
        font-size: 24px;
        margin-bottom: 5px;
        color: #ff3800;
      }
    }
  }
}

.logout-btn {
  button {
    width: 60%;
    margin: 10px auto;
    display: block;
    font-size: 13px;
    color: #616161;
    border-radius: 9px;
    border: 1px solid #dcdcdc;
    padding: 7px 0;
    text-align: center;
    background-color: #fafafa;
  }
}
</style>
3.用户退出登录

思路:用户退出登录时,要把存入的用户信息清空,把购物车商品列表也清空

  • 新建module/user.js中的actions方法,异步清空数据
//module/user.js
  actions: {
    logoutActions (ctx) {
      ctx.commit('setUserInfo', {})// 清空用户信息
      ctx.commit('/cart/setCartListMethods', [], { root: true })// 清空购物车商品数据
    }
  }
  • 页面"退出登录"按钮中绑定点击事件,事件内调用该actions方法

此处用到了vuex的跨模块访问

<div class="logout-btn">
  <button @click="logout">退出登录</button>
</div>
	......
    logout () {
      // 弹出确认框
      this.$dialog.confirm({
        title: '温馨提示',
        message: '你确认要退出么?'
      })
        .then(() => {
          this.$store.diapatch('user/logoutActions')
        })
        .catch(() => {})
    }

在这里插入图片描述

四.项目优化和项目打包

1.为什么要打包项目?

Vue脚手架仅在开发过程中协助开发,并不参与上线,
因为脚手架中包含许多开发时的依赖(如node),还有浏览器无法识别的语法(如ES6)
打包的作用就在于,可以:

  • 将多个文件雅俗合并成一个文件==>减少请求次数,减轻服务器压力
  • 语法降级
  • 解析less,sass,typescript语法为css和js代码
2.打包的流程是怎么样的?

vue2项目使用打包命令进行编译,最终生成浏览器能直接识别和运行的网页,被称为需要上线的源码(包括css,img,js文件夹,index.html等)
*上线:打包后的代码上传到运行在公网环境下的服务器中
在这里插入图片描述
打包后生成dist文件夹

3.打包的命令和配置有哪些?

命令:
vue脚手架工具提供了打包命令:根目录下执行:yarn build命令,会在项目根目录下自动创建一个文件夹dist,包含了打包后的文件,只需要放到服务器即可
配置:
默认情况下,需要放到服务器根目录打开,如希望双击运行,需要配置publicPath配成相对路径

//vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  publicPath: './',
  transpileDependencies: true
})

此时把项目放在任意盘符任意路径下都能运行

4.打包优化:路由懒加载
为什么要打包优化?

当打包构建应用时,js包会变得非常大,影响页面加载,
如果能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,无疑会更高效

如何实现打包优化?

step1:异步组件改造

示例:import Login from "@/view/login"
改成:const login=()=>import('@/views/login')

step2:路由中应用

import router=new VueRouter({
    routes:[
        ...
        {path:'productdetail',component:ProductDetail},
        {path:'pay',component:Pay}
    ]
})

step3:重新打包yarn build

如何取舍首次加载的组件?

跟首页相关的自动加载:Layout,Home,Category,Cart,user,其他的都按需加载

如何验证效果和前后对比?

前:控制台>network>(切换页面不会新加载任何组件,一开始已经加载过了)
在这里插入图片描述

后:切换页面,不断新加载js
在这里插入图片描述

五.知识点

1. Vue中的mixin

在 Vue 生态中,Mixin 提供了一种非常强大的机制,允许开发者提取公共逻辑并将其注入到任意数量的组件中。无论是数据 (data)、计算属性 (computed)、方法 (methods) 还是生命周期钩子都可以通过 Mixin 共享。

  • 特性说明

生命周期钩子合并:当组件和 Mixin 都定义了相同的生命周期钩子时,两者都会被执行,且 Mixin 的钩子优先于组件本身的钩子执行4。
数据冲突解决:如果 Mixin 和组件同时定义了同名的数据属性,则组件自身的定义会覆盖 Mixin 的定义;对于嵌套的对象类型数据,Vue 会对它们进行递归合并

用法说明:

export default {
    //此处编写vue组件实例的配置项data,computed,methods等等,通过一定语法,可以直接混入到组件内部
    注意事项
    //1.若配置项提供了与组件同名的data属性或methods方法(等),那么组件内的优先级比这个mixin的更高
    //2.如果编写了生命周期函数,则mixin中的钩子和页面的钩子会用数组管理起来统一执行
}
  • 总结

Mixin 在前端开发中扮演着重要角色,尤其是在需要跨组件共享逻辑时显得尤为有用。然而需要注意的是,过度依赖 Mixin 可能会使项目的结构变得难以维护,因此应谨慎权衡其使用的必要性和合理性

2.Vuex的跨模块访问

Vuex 跨模块调用 Action 和 Mutation
在 Vuex 中,跨模块访问是一个常见的需求。可以通过上下文中自带的对象来访问其他模块的内容。例如,在 actions 上下文中可以直接通过 commit 方法指定目标模块名称以及对应的 mutation

一句话总结

action方法在正常的提交mutation语句中,添加一句{root:true},代表调用的是全局的mutations方法
(前提是那个子模块开启了命名空间,即该模块的 namespaced 属性为 true)

示例:

//modules/user.js
  actions: {
    logoutActions (ctx) {
      ctx.commit('setUserInfo', {})// 调用本模块mutations方法
      ctx.commit('/cart/setCartListMethods', [], { root: true })// 调用cart模块mutations方法
    }
  }

//modules/cart.js
export default {
  namespaced: true,//必须开启命名空间
  mutations:{
	setCartListMethods(){
		...
	}
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端OnTheRun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值