Skip to content
On this page

订单模块

创建订单

image-20221204001914040

静态结构

src\pages\order\create\index.vue

vue
<template>
  <view class="order-create">
    <scroll-view scroll-y class="viewport">
      <!-- 收货地址 -->
      <navigator
        class="shipment"
        hover-class="none"
        url="/pages/my/address/index?from=order"
      >
        <view class="user"> {{ "收货人" }} 13xxxxxxxxxx </view>
        <view class="address">
          {{ "广东省广州市天河区" }}{{ "吉山街道黑马程序员" }}
        </view>
        <text class="icon icon-right"></text>
      </navigator>
      <!-- 收货地址 - 无地址 -->
      <navigator
        class="shipment"
        hover-class="none"
        url="/pages/my/address/index?from=order"
      >
        <view class="address"> 请选择收货地址 </view>
        <text class="icon icon-right"></text>
      </navigator>

      <!-- 商品信息 -->
      <view class="goods">
        <navigator
          v-for="item in []"
          :key="item.skuId"
          :url="`/pages/goods/index?id=${item.id}`"
          class="item"
          hover-class="none"
        >
          <image class="cover" :src="item.picture"></image>
          <view class="meta">
            <view class="name ellipsis">
              {{ item.name }}
            </view>
            <view class="type">{{ item.attrsText }}</view>
            <view class="price">
              <view class="actual">
                <text class="symbol">¥</text>{{ item.payPrice }}
              </view>
              <view class="original">
                <text class="symbol">¥</text>{{ item.price }}
              </view>
            </view>
            <view class="quantity">x{{ item.count }}</view>
          </view>
        </navigator>
      </view>
      
      <!-- 配送及支付方式 -->
      <view class="related">
        <view class="item">
          <text class="text">配送时间</text>
          <text class="picker icon-fonts">{{ "时间不限(周一至周五)" }}</text>
        </view>
        <view class="item">
          <text class="text">支付方式</text>
          <text class="picker icon-fonts">{{ "在线支付" }}</text>
        </view>
        <view class="item">
          <text class="text">买家备注</text>
          <input v-model="buyerMessage" cursor-spacing="30" placeholder="建议留言前先与商家沟通确认" />
        </view>
      </view>

      <!-- 支付金额 -->
      <view class="settlement">
        <view class="item">
          <text class="text">商品总价: </text>
          <text class="number">
            <text class="symbol">¥</text>
            {{ "782.00" }}
          </text>
        </view>
        <view class="item">
          <text class="text">运费: </text>
          <text class="number">
            <text class="symbol">¥</text>
            {{ "12.00" }}
          </text>
        </view>

        <!-- 如果有折扣,渲染折扣金额,没有折扣就不渲染了,免得用户伤心 -->
        <view class="item" v-if="true">
          <text class="text">折扣: </text>
          <text class="number danger">
            <text class="symbol"></text>
            {{ "12.00" }}
          </text>
        </view>
      </view>
    </scroll-view>

    <!-- 底部工具 -->
    <view class="toolbar">
      <view class="amount">
        <text class="symbol">¥</text>
        <text class="number">{{ 782.0 }}</text>
      </view>
      <view class="button">提交订单</view>
    </view>
  </view>
</template>
<script>
export default {
  data(){
    return {
          
      // 买家留言
      buyerMessage: "",
    }
  }
};
</script>
<style lang="scss">
.safe-area-bottom {
  background-color: #fff;
}
.order-create {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
  background-color: #f4f4f4;
}

.viewport {
  padding-top: 20rpx;
}

.shipment {
  padding: 30rpx 30rpx 25rpx 84rpx;
  margin: 0 20rpx;
  font-size: 26rpx;
  border-radius: 10rpx;
  background: url(https://static.botue.com/erabbit/static/images/locate.png)
    20rpx center / 50rpx no-repeat #fff;
  position: relative;
}

.shipment .icon {
  font-size: 36rpx;
  color: #333;
  transform: translateY(-50%);
  position: absolute;
  top: 50%;
  right: 20rpx;
}

.shipment .user {
  color: #333;
  margin-bottom: 5rpx;
}

.shipment .address {
  color: #666;
}

.link {
  margin: 20rpx;
  text-align: center;
  line-height: 72rpx;
  font-size: 26rpx;
  color: #fff;
  border-radius: 72rpx;
  background-color: #27ba9b;
}

.popup-root {
  .list {
    padding: 20rpx 0 40rpx 10rpx !important;
  }

  .list .item {
    padding: 30rpx 60rpx 30rpx 10rpx;
    position: relative;
  }

  .list .item .icon {
    color: #999;
    font-size: 40rpx;
    transform: translateY(-50%);
    position: absolute;
    top: 50%;
    right: 10rpx;
  }

  .list .item .icon-checked {
    color: #27ba9b;
  }

  .list .item .text {
    font-size: 28rpx;
    color: #444;
  }
}

.goods {
  margin: 20rpx 20rpx 0;
  padding: 0 20rpx;
  border-radius: 10rpx;
  background-color: #fff;
}

.goods .item {
  display: flex;
  padding: 30rpx 0;
  border-top: 1rpx solid #eee;
}

.goods .item:first-child {
  border-top: none;
}

.goods .item .cover {
  width: 170rpx;
  height: 170rpx;
  border-radius: 10rpx;
  margin-right: 20rpx;
}

.goods .item .meta {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  position: relative;
}

.goods .item .name {
  height: 80rpx;
  font-size: 26rpx;
  color: #444;
}

.goods .item .type {
  line-height: 1.8;
  padding: 0 15rpx;
  margin-top: 6rpx;
  font-size: 24rpx;
  align-self: flex-start;
  border-radius: 4rpx;
  color: #888;
  background-color: #f7f7f8;
}

.goods .item .price {
  display: flex;
  align-items: baseline;
  margin-top: 6rpx;
  font-size: 28rpx;
}

.goods .item .symbol {
  font-size: 20rpx;
}

.goods .item .original {
  font-size: 24rpx;
  color: #999;
  text-decoration: line-through;
}

.goods .item .actual {
  margin-right: 10rpx;
  color: #cf4444;
}

.goods .item .quantity {
  position: absolute;
  bottom: 0;
  right: 0;
  font-size: 26rpx;
  color: #444;
}

.related {
  padding: 0 20rpx;
  margin: 20rpx 20rpx 0;
  border-radius: 10rpx;
  background-color: #fff;
}

.related .item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  min-height: 80rpx;
  font-size: 26rpx;
  color: #333;
}

.related input {
  flex: 1;
  text-align: right;
  margin: 20rpx 0;
  padding-right: 20rpx;
  font-size: 26rpx;
  color: #999;
}

.related .item .text {
  width: 125rpx;
}

.related .picker {
  color: #666;
}

.related .picker::after {
  content: "\e6c2";
}

/* 结算清单 */
.settlement {
  padding: 0 20rpx;
  margin: 20rpx 20rpx 0;
  border-radius: 10rpx;
  background-color: #fff;
}

.settlement .item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 80rpx;
  font-size: 26rpx;
  color: #333;
}

.settlement .symbol {
  font-size: 80%;
}

.settlement .danger {
  color: #cf4444;
}

.gap {
  height: 20rpx;
}

.toolbar {
  height: 120rpx;
  padding: 0 40rpx;
  border-top: 1rpx solid #eaeaea;
  background-color: #fff;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.toolbar .amount {
  font-size: 40rpx;
  color: #cf4444;
}

.toolbar .amount .symbol,
.toolbar .amount .decimal {
  font-size: 75%;
}

.toolbar .button {
  width: 220rpx;
  text-align: center;
  line-height: 72rpx;
  font-size: 26rpx;
  color: #fff;
  border-radius: 72rpx;
  background-color: #27ba9b;
}
</style>

请求数据、渲染界面

1、封装API:src/api/cart.js

js
import http from "@/utils/http";

/**
 * 获取预付订单(填写订单)
 */
export const getPreOrderAPI = () => {
  return http({
    url: "/member/order/pre",
  });
};

2、封装请求函数

3、onShow触发请求

4、声明变量保存数据

src/pages/cart/index.vue

js
import { getPreOrderAPI } from "@/api/order";
/* 学习目标:请求数据、保存数据、渲染界面  */
export default {
  data() {
    return {
       buyerMessage: "",
      preOrder: null,
    };
  },
  methods: {
    async loadData() {
      const { result } = await getPreOrderAPI();
      this.preOrder = result;
    },
  },
  onShow() {
    this.loadData();
  },
};

5、渲染界面

改造地址管理

目标:支持点击事件

src/pages/my/address/index.vue

diff
<template>
  <view class="viewport">
    <!-- 地址列表 -->
    			<view 
    				class="item"
+          	@click="onSelect(item)"
          >
              <view class="user">
                {{ item.receiver }}
                <text>{{ item.contact }}</text>
                <text class="badge" v-if="item.isDefault"> 默认 </text>
              </view>
              <view class="locate">
                {{ item.fullLocation }} {{ item.address }}
              </view>
              <!-- 🐛 添加阻止冒泡 -->
              <navigator
+              	@click.stop
                :url="`./form?id=${item.id}`"
                class="edit"
                hover-class="none"
              >
                修改
              </navigator>
            </view>
    </scroll-view>
   
  </view>
</template>
diff
<script>

export default {

  methods: {
+    onSelect(item) {
+      if (item) {
+        console.log("item  ----->  ", item);
+        // TODO: 将item存入redux中
+      }
+    },

+  onLoad({ from }) {
+    this.from = from;
+  },
};

地址管理-选中数据存入vuex

1、addres模块中声明state、mutations

src/store/address.js

diff
export default {
  // 命名空间
  namespaced: true,
  state: {
+   selectedAddress: null,
  },
  mutations: {
+   onSelectAddress(state, payload) {
+     state.selectedAddress = payload;
+   },
  },
};

2、注册模块

src/store/index.js

diff
+import address from "./address";

const store = new Vuex.Store({

  modules: {
    user,
+   address,
  },

});

3、地址管理模块、选中事件、数据存入vuex中,并返回上一页

src/pages/my/address/index.vue

diff
methods: {
+   ...mapMutations("address", ["onSelectAddress"]),
    onSelect(item) {
      if (item) {
        // TODO: 将item存入redux中
+       this.onSelectAddress(item);
+       uni.navigateBack();
      }
    },

4、订单模块:根据vuex中是否有数据、条件渲染

src/pages/order/create/index.vue

js
computed: {
    ...mapState("address", ["selectedAddress"]),
  },
diff
<!-- 收货地址 -->
      <navigator
        class="shipment"
        hover-class="none"
        url="/pages/my/address/index?from=order"
+       v-if="selectedAddress"
      >
        <view class="user">
+         {{ selectedAddress.receiver }} {{ selectedAddress.contact }}
        </view>
        <view class="address">
+         {{ selectedAddress.fullLocation }}{{ selectedAddress.address }}
        </view>
        <text class="icon icon-right"></text>
      </navigator>
      <!-- 收货地址 - 无地址 -->
      <navigator
+       v-else
        class="shipment"
        hover-class="none"
        url="/pages/my/address/index?from=order"
      >
        <view class="address"> 请选择收货地址 </view>
        <text class="icon icon-right"></text>
      </navigator>

获取支付id

1、封装API

src/api/order.js

js
/**
 * 提交订单
 */
export const createOrderAPI = (data) => {
  return http({
    url: "/member/order",
    method: "POST",
    data,
  });
};

2、绑定提交事件

3、调用API

4、跳转到订单详情页面

js
async onSubmit() {
      if (!this.selectedAddress) {
        return uni.showToast({
          title: "请先选择收货地址",
        });
      }
      const { result } = await createOrderAPI({
        goods: this.preOrder.goods,
        addressId: this.selectedAddress.id,
        payType: 1,
        payChannel: 2,
        buyMessage: "尽快发货",
        deliveryTimeType: 1,
      });
      uni.showToast({ title: "创建订单成功" });
     	setTimeout(() => {
        uni.navigateTo({ url: `/pages/order/detail?orderId=${result.id}` });
      }, 1000);
    },

订单详情

image-20221204002816294

订单相关常量

src\pages\order\OrderConstance.js

js
// 订单状态
// 订单状态,0为全部 1为待付款、2为待发货、3为待收货、4为待评价、5为已完成、6为已取消
export const OrderState = {
  QuanBu: 0,
  DaiFuKuan: 1,
  DaiFaHuo: 2,
  DaiShouHuo: 3,
  DaiPingJia: 4,
  YiWanCheng: 5,
  YiQuXiao: 6,
};

// 订单状态描述
export const OrderStateOptions = {
  [OrderState.DaiFuKuan]: "待付款",
  [OrderState.DaiFaHuo]: "待发货",
  [OrderState.DaiShouHuo]: "待收货",
  [OrderState.DaiPingJia]: "待评价",
  [OrderState.YiWanCheng]: "已完成",
  [OrderState.YiQuXiao]: "已取消",
};

静态结构

src\pages\order\detail.vue

vue
<template>
  <view class="order-detail">
    <!-- 内容 -->
    <scroll-view class="viewport" scroll-y>
      <!-- 顶部导航栏 -->
      <view class="navbar" :style="{ paddingTop: safeArea.top + 'px' }">
        <view class="wrap">
          <navigator
            open-type="navigateBack"
            class="back icon-left"
          ></navigator>
          <view :class="['title', platform]"></view>
        </view>
      </view>
      <!-- 订单状态 -->
      <view class="overview" :style="{ paddingTop: safeArea.top + 40 + 'px' }">
        <template v-if="detail.orderState === OrderState.DaiFuKuan">
          <view class="status icon-clock">等待付款</view>
          <view class="tips">
            <text>应付金额: ¥{{ 0.01 }}</text>
            <text class="countdown"> 支付剩余  </text>
             <uni-countdown
              :minute="15"
              :second="00"
              :showDay="false"
              start
              color="#fff"
            />
          </view>
          <view class="button">去支付</view>
        </template>
        <template v-else>
          <view class="status icon-clock">
            {{ OrderStateOptions[detail.orderState] }}
          </view>
        </template>
      </view>
      <!-- 商品信息 -->
      <view class="goods">
        <view class="item">
          <navigator
            v-for="item in detail.skus"
            :key="item.id"
            :url="`/pages/goods/index?id=${item.spuId}`"
            hover-class="none"
          >
            <image class="cover" :src="item.image"></image>
            <view class="meta">
              <view class="name ellipsis">{{ item.name }}</view>
              <view class="type">{{ item.attrsText }}</view>
              <view class="price">
                <view class="original">
                  <text class="symbol">¥</text>
                  <text>{{ item.curPrice }}</text>
                </view>
                <view class="actual">
                  <text class="text">实付: </text>
                  <text class="symbol">¥</text>
                  <text>{{ item.realPay }}</text>
                </view>
              </view>
              <view class="quantity">x{{ item.quantity }}</view>
            </view>
          </navigator>
          <view
            class="action"
            v-if="detail.orderState === OrderState.YiWanCheng"
          >
            <view class="button primary">申请售后</view>
            <navigator url="/pages/comments/publish/index" class="button">
              去评价
            </navigator>
          </view>
        </view>
        <!-- 合计 -->
        <view class="total">
          <view class="row">
            <view class="text">商品总价: </view>
            <view class="symbol">{{ detail.totalMoney }}</view>
          </view>
          <view class="row">
            <view class="text">运费: </view>
            <view class="symbol">{{ detail.postFee }}</view>
          </view>
          <view class="row paid">
            <view class="text">实付: </view>
            <view class="symbol primary">{{ detail.payMoney }}</view>
          </view>
        </view>
      </view>
      <!-- 订单信息 -->
      <view class="detail">
        <view class="title">订单信息</view>
        <view class="row">
          <view>订单编号: {{ detail.id }}</view>
          <view>下单时间: {{ detail.createTime }}</view>
          <view >
            支付方式: 在线支付
          </view>
          <view >
            支付渠道: 微信支付
          </view>
        </view>
      </view>
    </scroll-view>

    <!-- 底部工具 -->
    <view class="toobar" v-if="detail.orderState === OrderState.DaiFuKuan">
      <view @tap="orderPay" class="primary">去支付</view>
      <view class="default">取消订单</view>
    </view>
  </view>
</template>
<script>
import { OrderStateOptions, OrderState } from "./OrderConstance";
import { mapState } from "vuex";
export default {
  computed: {
    ...mapState(["safeArea", "platform"]),
  },
  data(){
    return {
      detail: null,
      OrderStateOptions,
      OrderState
    }
  }
};
</script>
<style lang="scss">
.order-detail {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.navbar {
  width: 750rpx;
  color: #fff;
  background-image: url(https://static.botue.com/erabbit/static/images/order_bg.png);
  background-size: cover;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9;
  .title {
    height: 40px;
    line-height: 30px;
    text-align: center;
    font-size: 17px;
    font-weight: 500;
  }
  .android {
    text-align: left;
    padding-left: 42px;
  }
  .wrap {
    position: relative;
  }
  .back {
    position: absolute;
    left: 10px;
    top: 4px;
    line-height: 1;
    font-size: 23px;
    z-index: 9;
  }
}
.viewport {
  background-color: #f7f7f8;
}
.overview {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
  padding-bottom: 30rpx;
  color: #fff;
  background-image: url(https://static.botue.com/erabbit/static/images/order_bg.png);
  background-size: cover;
  .status {
    font-size: 36rpx;
    &::before {
      margin-right: 6rpx;
      font-weight: 500;
    }
  }
  .tips {
    margin-top: 30rpx;
    font-size: 24rpx;
    width: 100%;
    text-align: center;
    display: flex;
    justify-content: center;
    align-items: center;
    .countdown {
      margin-left: 10rpx;
    }
  }
  .button {
    width: 260rpx;
    height: 64rpx;
    text-align: center;
    line-height: 64rpx;
    margin-top: 30rpx;
    font-size: 28rpx;
    color: #27ba9b;
    border-radius: 68rpx;
    background-color: #fff;
  }
}
.shipment {
  line-height: 1.4;
  padding: 0 20rpx;
  margin: 20rpx 20rpx 0;
  border-radius: 10rpx;
  background-color: #fff;
  .locate {
    background-image: url(https://static.botue.com/erabbit/static/images/locate.png);
    .user {
      font-size: 26rpx;
      color: #444;
    }
    .address {
      font-size: 24rpx;
      color: #666;
    }
  }
  .logistics {
    background-image: url(https://static.botue.com/erabbit/static/images/car.png);
    border-bottom: 1rpx solid #eee;
    position: relative;
    &::after {
      position: absolute;
      right: 10rpx;
      top: 50%;
      transform: translateY(-50%);
      content: "\e6c2";
      font-family: "erabbit" !important;
      font-size: 32rpx;
      color: #666;
    }
    .message {
      font-size: 26rpx;
      color: #444;
    }
    .date {
      font-size: 24rpx;
      color: #666;
    }
  }
}
.shipment .locate,
.shipment .logistics {
  min-height: 120rpx;
  padding: 30rpx 30rpx 25rpx 75rpx;
  background-size: 50rpx;
  background-repeat: no-repeat;
  background-position: 6rpx center;
}
.goods {
  margin: 20rpx 20rpx 0;
  padding: 0 20rpx;
  border-radius: 10rpx;
  background-color: #fff;
  .item {
    padding: 30rpx 0;
    border-bottom: 1rpx solid #eee;
    navigator {
      display: flex;
    }
    .cover {
      width: 170rpx;
      height: 170rpx;
      border-radius: 10rpx;
      margin-right: 20rpx;
    }
    .meta {
      flex: 1;
      display: flex;
      flex-direction: column;
      justify-content: center;
      position: relative;
    }
    .name {
      height: 80rpx;
      font-size: 26rpx;
      color: #444;
    }
    .type {
      line-height: 1.8;
      padding: 0 15rpx;
      margin-top: 6rpx;
      font-size: 24rpx;
      align-self: flex-start;
      border-radius: 4rpx;
      color: #888;
      background-color: #f7f7f8;
    }
    .price {
      display: flex;
      margin-top: 6rpx;
      font-size: 24rpx;
    }
    .symbol {
      font-size: 20rpx;
    }
    .original {
      color: #999;
      text-decoration: line-through;
    }
    .actual {
      margin-left: 10rpx;
      color: #444;
    }
    .text {
      font-size: 22rpx;
    }
    .quantity {
      position: absolute;
      bottom: 0;
      right: 0;
      font-size: 24rpx;
      color: #444;
    }
    .action {
      display: flex;
      flex-direction: row-reverse;
      justify-content: flex-start;
      padding: 30rpx 0 0;
    }
  }
  .action {
    .button {
      width: 200rpx;
      height: 60rpx;
      text-align: center;
      justify-content: center;
      line-height: 60rpx;
      margin-left: 20rpx;
      border-radius: 60rpx;
      border: 1rpx solid #ccc;
      font-size: 26rpx;
      color: #444;
    }
    .primary {
      color: #27ba9b;
      border-color: #27ba9b;
    }
  }
  .total {
    line-height: 1;
    font-size: 26rpx;
    padding: 20rpx 0;
    color: #666;
    .row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 10rpx 0;
    }
    .symbol {
      &::before {
        content: "¥";
        font-size: 20rpx;
      }
    }
    .paid {
      font-size: 30rpx;
      color: #444;
    }
    .primary {
      color: #cf4444;
    }
  }
}
.detail {
  line-height: 1;
  padding: 30rpx 20rpx 0;
  margin: 20rpx 20rpx 0;
  font-size: 26rpx;
  color: #666;
  border-radius: 10rpx;
  background-color: #fff;
  .title {
    font-size: 30rpx;
    color: #444;
  }
  .row {
    padding: 20rpx 0;
    & > view {
      display: block;
      padding: 10rpx 0;
    }
  }
}
.toobar {
  display: flex;
  flex-direction: row-reverse;
  justify-content: flex-start;
  padding: 30rpx 20rpx;
  background-color: #fff;
  box-shadow: 0 -4rpx 6rpx rgba(240, 240, 240, 0.6);
  position: relative;
  z-index: 9;
  & > view {
    width: 200rpx;
    height: 72rpx;
    text-align: center;
    line-height: 72rpx;
    margin-left: 15rpx;
    font-size: 26rpx;
    border-radius: 72rpx;
  }
  .default {
    color: #444;
    border: 1rpx solid #ccc;
  }
  .primary {
    color: #fff;
    background-color: #27ba9b;
  }
}
.popup-root {
  padding: 30rpx 30rpx 0;
  border-radius: 10rpx 10rpx 0 0;
  overflow: hidden;
  .title {
    font-size: 30rpx;
    text-align: center;
    margin-bottom: 30rpx;
  }
  .description {
    font-size: 28rpx;
    padding: 0 20rpx;
    .tips {
      color: #444;
      margin-bottom: 12rpx;
    }
    .cell {
      display: flex;
      justify-content: space-between;
      align-items: center;
      line-height: 1;
      padding: 15rpx 0;
      margin-bottom: 4rpx;
      color: #666;
    }
    .icon-ring {
      font-size: 38rpx;
      color: #999;
    }
    .icon-checked {
      font-size: 38rpx;
      color: #27ba9b;
    }
  }
  .footer {
    display: flex;
    justify-content: space-between;
    padding: 30rpx 0 40rpx;
    font-size: 28rpx;
    color: #444;
    .button {
      flex: 1;
      height: 72rpx;
      text-align: center;
      line-height: 72rpx;
      margin: 0 20rpx;
      color: #444;
      border-radius: 72rpx;
      border: 1rpx solid #ccc;
    }
    .primary {
      color: #fff;
      background-color: #27ba9b;
      border: none;
    }
  }
}
</style>

请求订单详情、渲染界面

1、封装API:src/api/order.js

js
/**
 * 获取订单详情
 */
export const geOrderDetailAPI = (id) => {
  return http({
    url: `/member/order/${id}`,
  });
};

2、封装请求函数

3、onShow触发请求

4、保存数据

src/pages/cart/index.vue

js
import { geOrderDetailAPI } from "@/api/order";
export default {
  data() {
    return {
      detail: null, 
    };
  },
  async onLoad({ id }) {
    console.log("id  ----->  ", id);
    // 💥💥 先写死一个id,方便调试, 注意使用字符串, 不要使用数字
    id = "1634489003572989954";
    const { result } = await geOrderDetailAPI(id);
    this.detail = result;
  },
};

5、渲染界面

渲染倒计时

1、根据dayjs,定义计算属性countdown

diff
computed: {
    ...mapState(["safeArea", "platform"]),
+   countdown() {
+     if (this.detail) {
+       const res = dayjs.unix(this.detail.countdown).format("mm ss");
+       return res.split(" ");
+     } else {
+       return [];
+     }
+   },
  },

2、绑定给倒计时组件

diff
<uni-countdown
+             :minute="countdown[0]"
+             :second="countdonw[1]"
              :showDay="false"
              start
              color="#fff"
            />

实现支付

文档:微信支付

注意:

💥💥必须使用企业开发者账号登录

💥💥 删除测试id

前置步骤:

  1. 改造登录方法,token存入vuex中
diff
async onGetPhone(e) {
      const { iv, encryptedData } = e.detail;
      const [err, res] = await uni.login();
      if (!err) {
        const {result} = await loginByPhoneAPI({
          iv,
          encryptedData,
          code: res.code,
        });
        
+       this.$store.commit("user/saveProfile", result);

        uni.showToast({
          title: "登录成功",
          icon: "success",
        });

        setTimeout(() => {
          uni.navigateBack();
        }, 1500);
      }
    },

步骤:

  1. 封装API,获取微信支付所需参数
  2. 调用 uni.requestPayment 调起微信支付
  3. 支付成功,弹出提示,跳转到支付成功页面 /pages/order/payment

代码:

js
/**
 * 支付-微信-小程序
 */
export const getPayWxPayMiniPay = (orderId) => {
  return http({
    url: `/pay/wxPay/miniPay`,
    data: { orderId },
  });
};
js
async orderPay() {
      const res = await getPayWxPayMiniPay(this.order.id);
      console.log("res -----> ", res);
      const [err, result] = await uni.requestPayment(res.result);
      if (!err) {
        uni.showToast({ title: "支付成功", icon: "success" });
        setTimeout(() => {
          u
        })
      } else {
        uni.showToast({ title: "支付失败", icon: "error" });
      }
    },

支付成功

src\pages\order\payment.vue

image-20221204130614159

支付成功代码(静态)

只需要实现

  1. 返回首页
  2. 跳转到订单页面
vue
<template>
  <view class="payment">
    <view class="navbar" :style="{ paddingTop: safeArea.top + 'px' }">
      <view class="wrap">
        <navigator class="back icon-left" @click="goBack"></navigator>
        <view :class="['title', platform]">支付成功</view>
      </view>
    </view>

    <scroll-view
      class="viewport"
      id="scrollView"
      enhanced
      scroll-y
      :show-scrollbar="false"
    >
      <!-- 订单状态 -->
      <view
        class="overview"
        :style="{ paddingTop: safeArea.top + 40 + 'px' }"
      >
        <view class="status icon-checked">支付成功</view>
        <view class="buttons">
          <navigator
            hover-class="none"
            class="button"
            open-type="switchTab"
            url="/pages/index/index"
          >
            返回首页
          </navigator>
          <navigator hover-class="none" url="/pages/order/index">
            查看订单
          </navigator>
        </view>
      </view>
    </scroll-view>
  </view>
</template>
<script>
import { mapState } from "vuex";
export default {
  computed: {
    ...mapState(["safeArea"]),
  },
  data() {
    return {};
  },
};
</script>
<style lang="scss">
.payment {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
}

.navbar {
  width: 750rpx;
  color: #fff;
  background-color: #27ba9b;

  position: fixed;
  top: 0;
  left: 0;
  z-index: 9;
}

.navbar .title {
  height: 40px;
  line-height: 30px;
  text-align: center;
  font-size: 17px;
  font-weight: 500;
  opacity: 0;
}

.navbar .android {
  text-align: left;
  padding-left: 42px;
}

.navbar .wrap {
  position: relative;
}

.navbar .back {
  position: absolute;
  left: 10px;
  top: 4px;
  line-height: 1;
  font-size: 23px;
  z-index: 9;
}

.viewport {
  background-color: #f7f7f8;
}

.overview {
  line-height: 1;
  padding-bottom: 70rpx;
  color: #fff;
  background-color: #27ba9b;
}

.overview .status {
  font-size: 36rpx;
  font-weight: 500;
  text-align: center;
}

.overview .status::before {
  display: block;
  font-size: 110rpx;
  margin-bottom: 20rpx;
}

.overview .buttons {
  height: 60rpx;
  line-height: 60rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  margin-top: 60rpx;
}

.overview navigator {
  text-align: center;
  margin: 0 10rpx;
  font-size: 28rpx;
  color: #fff;

  &:first-child {
    width: 200rpx;
    border-radius: 64rpx;
    border: 1rpx solid #fff;
  }
}
</style>

拓展-uni-app 多端开发

条件编译

条件编译是用特殊的注释作为标记,在编译时根据这些特殊的注释,将注释里面的代码编译到不同平台。

**写法:**以 #ifdef 或 #ifndef 加 %PLATFORM% 开头,以 #endif 结尾。

  • #ifdef:if defined 仅在某平台存在
  • #ifndef:if not defined 除了某平台均存在
  • %PLATFORM%:平台名称
条件编译写法说明
#ifdef APP-PLUS 需条件编译的代码 #endif仅出现在 App 平台下的代码
#ifndef H5 需条件编译的代码 #endif除了 H5 平台,其它平台均存在的代码
#ifdef H5 || MP-WEIXIN 需条件编译的代码 #endif在 H5 平台或微信小程序平台存在的代码(这里只有||,不可能出现&&,因为没有交集)

%PLATFORM% 可取值如下:

平台
APP-PLUSApp
APP-PLUS-NVUEApp nvue
H5H5
MP-WEIXIN微信小程序
MP-ALIPAY支付宝小程序
MP-BAIDU百度小程序
MP-TOUTIAO字节跳动小程序
MP-QQQQ小程序
MP-360360小程序
MP微信小程序/支付宝小程序/百度小程序/字节跳动小程序/QQ小程序/360小程序
quickapp-webview快应用通用(包含联盟、华为)
quickapp-webview-union快应用联盟
quickapp-webview-huawei快应用华为

支持的文件

  • .vue
  • .js
  • .css
  • pages.json
  • 各预编译语言文件,如:.scss、.less、.stylus、.ts、.pug

注意: 条件编译是利用注释实现的,在不同语法里注释写法不一样,js使用 // 注释、css 使用 /* 注释 */、vue/nvue 模板里使用 <!-- 注释 -->

API 的条件编译

javascript
// #ifdef  %PLATFORM%
平台特有的API实现
// #endif

示例,如下代码仅在 App 下出现:

img

示例,如下代码不会在 H5 平台上出现:

img

除了支持单个平台的条件编译外,还支持多平台同时编译,使用 || 来分隔平台名称。

示例,如下代码会在 App 和 H5 平台上出现:

img

组件的条件编译

html
<!--  #ifdef  %PLATFORM% -->
平台特有的组件
<!--  #endif -->

示例,如下公众号关注组件仅会在微信小程序中出现:

html
<view>
    <view>微信公众号关注组件</view>
    <view>
        <!-- uni-app未封装,但可直接使用微信原生的official-account组件-->
        <!-- #ifdef MP-WEIXIN -->
                <official-account></official-account>
            <!-- #endif -->
    </view>
</view>

样式的条件编译

css
/*  #ifdef  %PLATFORM%  */
平台特有样式
/*  #endif  */

注意: 样式的条件编译,无论是 css 还是 sass/scss/less/stylus 等预编译语言中,必须使用 /*注释*/ 的写法。

正确写法

img

错误写法

img

pages.json 的条件编译

下面的页面,只有运行至 App 时才会编译进去。

img

Released under the MIT License.