liujie
23 小时以前 384bce80449b17651937429038c4370dbce60e7b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
<template>
    <view v-if="visible" class="stock-toast" @tap="_handleGoList">
        <!-- 左侧:图标 + 文案(单笔缺货额外展示支付倒计时) -->
        <view class="stock-toast-left">
            <view class="stock-toast-icon"></view>
            <text class="stock-toast-title">{{ titleText }}</text>
            <!-- 单笔缺货:展示支付倒计时;多笔缺货:仅展示具体数量,不展示倒计时 -->
            <view v-if="count === 1" class="stock-toast-countdown">
                <view class="countdown-block">{{ minuteText }}</view>
                <text class="countdown-colon">:</text>
                <view class="countdown-block">{{ secondText }}</view>
            </view>
        </view>
        <!-- 右侧:前往支付按钮 -->
        <view class="stock-toast-btn">
            <text class="stock-toast-btn-text">前往支付</text>
            <view class="stock-toast-btn-arrow"></view>
        </view>
    </view>
</template>
 
<script setup>
/**
 * 全局缺货订单提示(Toast)
 * 规则:存在缺货订单的所有页面均弹出;点击跳转缺货订单列表页;
 *      单笔展示支付倒计时,多笔仅展示具体数量、不展示倒计时。
 * 用法:在各业务页面模板中直接引入 <stock-order-toast /> 即可。
 */
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { getStockOrderCount, STOCK_ORDER_LIST_PAGE } from '@/utils/stock-order.js'
 
/** 缺货订单数量(Mock 数据源,接口就绪后由数据层替换) */
const count = ref(getStockOrderCount())
 
/** 是否存在缺货订单 */
const visible = computed(() => count.value > 0)
 
/** 提示文案:单笔用「一个」,多笔展示具体数量 */
const titleText = computed(() =>
    count.value > 1 ? `您有${count.value}个缺货订单待支付` : '您有一个缺货订单待支付'
)
 
// ======================== 支付倒计时(仅单笔缺货时展示) ========================
 
/** 倒计时初始剩余秒数(Mock 30 分钟) */
const COUNTDOWN_SECONDS = 30 * 60
 
const remainSeconds = ref(COUNTDOWN_SECONDS)
let countdownTimer = null
 
/** 剩余分钟(补零两位) */
const minuteText = computed(() => String(Math.floor(remainSeconds.value / 60)).padStart(2, '0'))
/** 剩余秒数(补零两位) */
const secondText = computed(() => String(remainSeconds.value % 60).padStart(2, '0'))
 
/** 启动倒计时(多笔缺货时按规则不展示倒计时,无需启动) */
const _startCountdown = () => {
    if (count.value !== 1) return
    countdownTimer = setInterval(() => {
        if (remainSeconds.value <= 0) {
            clearInterval(countdownTimer)
            countdownTimer = null
            return
        }
        remainSeconds.value -= 1
    }, 1000)
}
 
onMounted(_startCountdown)
onUnmounted(() => {
    if (countdownTimer) clearInterval(countdownTimer)
})
 
/** 跳转缺货订单列表页 */
const _handleGoList = () => {
    uni.navigateTo({ url: STOCK_ORDER_LIST_PAGE })
}
</script>
 
<style scoped lang="scss">
/* 通栏胶囊:距原生 tabBar 24rpx,左右各 24rpx,毛玻璃白底 + 柔和投影 */
.stock-toast {
    position: fixed;
    left: 24rpx;
    right: 24rpx;
    /* 原生 tabBar 由系统渲染在 webview 之外,内容区 bottom 即 tabBar 上沿 */
    bottom: calc(24rpx + constant(safe-area-inset-bottom));
    bottom: calc(24rpx + env(safe-area-inset-bottom));
    z-index: 85;
    height: 80rpx;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 16rpx 0 24rpx;
    border-radius: 40rpx;
    background: linear-gradient(135deg, rgba(255, 255, 255, 0.96) 0%, rgba(255, 255, 255, 0.88) 100%);
    border: 2rpx solid #ffffff;
    box-shadow: 0 8rpx 28rpx rgba(15, 41, 66, 0.13);
}
 
.stock-toast-left {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
}
 
/* 前置图标:lucide clipboard-list 15×15pt */
.stock-toast-icon {
    width: 30rpx;
    height: 30rpx;
    margin-right: 13rpx;
    flex-shrink: 0;
    background-repeat: no-repeat;
    background-position: center center;
    background-size: 100% 100%;
    background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMEYxNzJBIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHJlY3Qgd2lkdGg9IjgiIGhlaWdodD0iNCIgeD0iOCIgeT0iMiIgcng9IjEiIHJ5PSIxIi8+PHBhdGggZD0iTTE2IDRoMmEyIDIgMCAwIDEgMiAydjE0YTIgMiAwIDAgMS0yIDJINmEyIDIgMCAwIDEtMi0yVjZhMiAyIDAgMCAxIDItMmgyIi8+PHBhdGggZD0iTTEyIDExaDQiLz48cGF0aCBkPSJNMTIgMTZoNCIvPjxwYXRoIGQ9Ik04IDExaC4wMSIvPjxwYXRoIGQ9Ik04IDE2aC4wMSIvPjwvc3ZnPg==');
}
 
.stock-toast-title {
    font-size: 22rpx;
    font-weight: 700;
    line-height: 1;
    color: #0f172a;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
 
/* 倒计时组合:红底数字块 + 冒号 */
.stock-toast-countdown {
    display: flex;
    align-items: center;
    margin-left: 13rpx;
    flex-shrink: 0;
}
 
.countdown-block {
    width: 32rpx;
    height: 28rpx;
    border-radius: 6rpx;
    background: #e53935;
    color: #ffffff;
    font-size: 19rpx;
    font-weight: 700;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}
 
.countdown-colon {
    margin: 0 4rpx;
    font-size: 20rpx;
    font-weight: 700;
    line-height: 1;
    color: #e53935;
}
 
/* 前往支付按钮:红色渐变胶囊 */
.stock-toast-btn {
    flex-shrink: 0;
    height: 50rpx;
    padding: 0 18rpx;
    border-radius: 25rpx;
    display: flex;
    align-items: center;
    background: linear-gradient(90deg, #ef4444 0%, #dc2626 100%);
    box-shadow: 0 3rpx 10rpx rgba(220, 38, 38, 0.21);
}
 
.stock-toast-btn-text {
    font-size: 21rpx;
    font-weight: 700;
    line-height: 1;
    color: #ffffff;
}
 
/* 按钮内箭头:lucide chevron-right 9×9pt */
.stock-toast-btn-arrow {
    width: 18rpx;
    height: 18rpx;
    margin-left: 4rpx;
    background-repeat: no-repeat;
    background-position: center center;
    background-size: 100% 100%;
    background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTkgMTggNi02LTYtNiIvPjwvc3ZnPg==');
}
</style>