倒计时 + 步骤条:订单页黄金组合
电商类小程序的订单详情页,几乎都长同一个样子:顶部一圈支付倒计时,下方一条订单状态步骤条。Vant 的 CountDown 和 Steps 两个组件正好覆盖这套组合,本文以订单支付流程为主线把两者讲透。
CountDown 基础:倒计时支付截止时间
核心属性只有一个 time,单位是毫秒。常用姿势是拿后端返回的截止时间戳换算剩余毫秒:
html
order-pay.vue
<template>
<div class="pay-header">
<p>订单提交成功,请在</p>
<van-count-down :time="remainMs" format="HH:mm:ss" @finish="onTimeout" />
<p>内完成支付</p>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
const router = useRouter();
const remainMs = ref(0);
onMounted(() => {
// expireAt 为后端返回的支付截止时间戳(毫秒)
const expireAt = 1799999999000;
remainMs.value = Math.max(0, expireAt - Date.now());
});
const onTimeout = () => {
// 倒计时结束:跳转回订单列表或提示订单已关闭
router.replace("/order/closed");
};
</script>
不要用「本地时间 + 固定 30 分钟」算剩余时间,手机本地时间可能不准。以服务端时间戳做差值是唯一可靠的方式。
format 与自定义内容
format 支持 DD、HH、mm、ss、S 等占位符;需要「时:分:秒 各自带样式」时用默认插槽:
html
demo.vue
<!-- 自定义格式:天 + 时:分 -->
<van-count-down :time="remainMs" format="DD 天 HH 时 mm 分" />
<!-- 自定义样式:数字卡片风格 -->
<van-count-down :time="remainMs" :millisecond="false">
<template #default="current">
<span class="time-box">{{ pad(current.hours) }}</span>
<i class="colon">:</i>
<span class="time-box">{{ pad(current.minutes) }}</span>
<i class="colon">:</i>
<span class="time-box">{{ pad(current.seconds) }}</span>
</template>
</van-count-down>
<script setup>
const pad = (n) => String(n).padStart(2, "0");
</script>
<style scoped>
.time-box {
background: #323233; color: #fff;
border-radius: 4px; padding: 2px 6px; font-weight: 600;
}
.colon { color: #323233; margin: 0 4px; }
</style>
change 与 finish 事件
change 每秒触发(开启 millisecond 后每帧触发),回调参数包含 total、days、hours、minutes、seconds;finish 在归零时触发一次,用来做「支付超时」跳转或刷新订单状态。
Steps:订单状态步骤条
van-steps 通过 active 属性指定当前进行到第几步(从 0 开始):
html
order-status.vue
<template>
<van-steps :active="stepIndex" active-color="#ee0a24" active-icon="checked">
<van-step>提交订单</van-step>
<van-step>付款成功</van-step>
<van-step>商家发货</van-step>
<van-step>确认收货</van-step>
</van-steps>
</template>
<script setup>
import { computed, ref } from "vue";
// status: 1 待支付 2 待发货 3 待收货 4 已完成
const status = ref(2);
const stepIndex = computed(() => ({ 1: 0, 2: 1, 3: 2, 4: 3 }[status.value] ?? 0));
</script>
每一步显示时间或描述
van-step 支持在插槽里放多行内容,物流信息直接展示在步骤下:
html
demo.vue
<van-steps :active="1" direction="vertical">
<van-step>
已签收
<template #default>
<p class="log">您的订单已送达小区驿站,凭取件码 8-2-3006 领取。</p>
<p class="time">11-09 09:12</p>
</template>
</van-step>
<van-step>
运输中
<template #default>
<p class="log">包裹已到达【广州白云转运中心】</p>
<p class="time">11-08 21:40</p>
</template>
</van-step>
<van-step>
已发货
<template #default>
<p class="log">商家已发货,快递单号 SF1388xxxx</p>
<p class="time">11-08 10:05</p>
</template>
</van-step>
</van-steps>
| 属性(Steps) | 说明 |
|---|---|
| active | 当前步骤索引,从 0 开始 |
| direction | horizontal 横向 / vertical 纵向 |
| active-color / inactive-color | 激活与未激活的颜色 |
| active-icon / inactive-icon | 激活与未激活的图标 |
| finish-icon | 已完成步骤的图标 |
组合实战:待支付订单详情页
把两个组件拼成完整的订单页骨架:倒计时归零自动切状态,步骤条随支付状态推进:
html
order-detail.vue
<template>
<header class="status-header" :class="{ expired: expired }">
<template v-if="!expired">
<p class="status-text">等待付款</p>
<p class="count-line">
剩余
<van-count-down
:time="remainMs"
format="mm 分 ss 秒"
style="--van-count-down-text-color: #fff"
@finish="expired = true"
/>
自动关闭订单
</p>
</template>
<template v-else>
<p class="status-text">订单已超时关闭</p>
</template>
</header>
<van-steps :active="stepIndex" active-color="#07c160">
<van-step>提交订单</van-step>
<van-step>付款成功</van-step>
<van-step>商家发货</van-step>
<van-step>交易完成</van-step>
</van-steps>
<footer v-if="!expired">
<van-button round type="danger" block @click="onPay">
立即支付 ¥{{ amount }}
</van-button>
</footer>
</template>
<script setup>
import { computed, onMounted, ref } from "vue";
import { showToast } from "vant";
const props = defineProps({
status: { type: Number, default: 1 },
expireAt: { type: Number, required: true },
amount: { type: String, default: "0.00" },
});
const expired = ref(false);
const remainMs = ref(0);
onMounted(() => {
remainMs.value = Math.max(0, props.expireAt - Date.now());
if (remainMs.value === 0) expired.value = true;
});
const stepIndex = computed(() =>
({ 1: 0, 2: 1, 3: 2, 4: 3 }[props.status] ?? 0)
);
const onPay = () => showToast("调起收银台...");
</script>
关键点:倒计时 finish 与 expired 状态联动,超时后立即隐藏支付按钮并切换步骤条文案,避免用户对已关闭订单继续支付。
CountDown 属性速查
| 属性 / 事件 | 说明 |
|---|---|
| time | 倒计时时长,毫秒 |
| format | 格式:DD 天 HH 时 mm 分 ss 秒 |
| millisecond | 是否开启毫秒级渲染 |
| auto-start | 是否自动开始,false 时配合 ref.start() 手动开始 |
| @change / @finish | 每秒触发 / 归零触发 |
总结
CountDown 记住三件事:time 单位毫秒、剩余时间用服务端时间戳算差值、finish 里做超时兜底;Steps 记住一件事:active 是索引,用 computed 把后端状态码映射过去。两者组合出的订单页,就是移动端电商最常见的状态机界面。
把状态可视化,用户就不需要客服。