feat: 盒柜接入运费校验并支持一键合成
本次提交同步补齐小程序端对后端新能力的接入,既支持碎片一键合成,也支持盒柜发货前按商品分类动态判断是否必须支付运费。 - 合成页:新增一键合成入口,展示最大可合成次数,并将单次合成与批量合成交互拆分为更清晰的双按钮布局 - 盒柜页:碎片合成区同步支持批量合成,合成成功后同时刷新配方列表与背包数据 - 运费流程:发货前先调用后端运费检查接口,根据“件数不足”或“包含不包邮商品”展示不同确认文案,再决定是否创建运费订单 - API 封装:补充批量合成与运费检查接口,确保前端逻辑与后端规则保持一致
This commit is contained in:
parent
eca0561cd9
commit
575ccb2cfa
@ -154,6 +154,10 @@ export function requestShipping(user_id, ids, address_id) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/inventory/request-shipping`, method: 'POST', data })
|
||||
}
|
||||
|
||||
export function checkShippingFee(user_id, ids) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/inventory/shipping-fee/check`, method: 'POST', data: { inventory_ids: ids } })
|
||||
}
|
||||
|
||||
export function createShippingFeeOrder(user_id, ids) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/inventory/shipping-fee/preorder`, method: 'POST', data: { inventory_ids: ids } })
|
||||
}
|
||||
|
||||
@ -8,6 +8,10 @@ export function doSynthesis(userId, recipeId) {
|
||||
return authRequest({ url: `/api/app/users/${userId}/synthesis/do`, method: 'POST', data: { recipe_id: recipeId } })
|
||||
}
|
||||
|
||||
export function doBatchSynthesis(userId, recipeId) {
|
||||
return authRequest({ url: `/api/app/users/${userId}/synthesis/do-batch`, method: 'POST', data: { recipe_id: recipeId } })
|
||||
}
|
||||
|
||||
export function getSynthesisLogs(userId, page = 1, pageSize = 20) {
|
||||
return authRequest({ url: `/api/app/users/${userId}/synthesis/logs`, method: 'GET', data: { page, page_size: pageSize } })
|
||||
}
|
||||
|
||||
@ -93,16 +93,30 @@
|
||||
|
||||
<!-- 底部:进度 + 按钮 -->
|
||||
<view class="ticket-footer">
|
||||
<text class="ready-hint">
|
||||
{{ getReadyCount(recipe) }}/{{ recipe.materials?.length || 0 }} 材料就绪
|
||||
</text>
|
||||
<view
|
||||
class="synth-btn"
|
||||
:class="recipe.can_synthesize ? 'btn-ready' : 'btn-locked'"
|
||||
@tap="onSynthesize(recipe)"
|
||||
>
|
||||
<text class="btn-text">{{ synthesizing ? '合成中' : (recipe.can_synthesize ? '合成' : '不足') }}</text>
|
||||
<view v-if="recipe.can_synthesize" class="btn-shine"></view>
|
||||
<view class="ready-meta">
|
||||
<text class="ready-hint">
|
||||
{{ getReadyCount(recipe) }}/{{ recipe.materials?.length || 0 }} 材料就绪
|
||||
</text>
|
||||
<text class="batch-hint" v-if="getMaxSynthesizeCount(recipe) > 0">
|
||||
最多可合成 {{ getMaxSynthesizeCount(recipe) }} 次
|
||||
</text>
|
||||
</view>
|
||||
<view class="action-group">
|
||||
<view
|
||||
class="synth-btn synth-btn-secondary"
|
||||
:class="recipe.can_synthesize && !batchSynthesizing ? 'btn-ready' : 'btn-locked'"
|
||||
@tap="onSynthesize(recipe)"
|
||||
>
|
||||
<text class="btn-text">{{ synthesizing ? '合成中' : (recipe.can_synthesize ? '单次合成' : '不足') }}</text>
|
||||
</view>
|
||||
<view
|
||||
class="synth-btn synth-btn-primary"
|
||||
:class="getMaxSynthesizeCount(recipe) > 0 && !synthesizing ? 'btn-ready' : 'btn-locked'"
|
||||
@tap="onBatchSynthesize(recipe)"
|
||||
>
|
||||
<text class="btn-text">{{ batchSynthesizing ? '批量中' : (getMaxSynthesizeCount(recipe) > 0 ? '一键合成' : '不足') }}</text>
|
||||
<view v-if="getMaxSynthesizeCount(recipe) > 0 && !batchSynthesizing" class="btn-shine"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -115,10 +129,11 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getSynthesisRecipes, doSynthesis } from '../../api/synthesis.js'
|
||||
import { getSynthesisRecipes, doSynthesis, doBatchSynthesis } from '../../api/synthesis.js'
|
||||
|
||||
const loading = ref(true)
|
||||
const synthesizing = ref(false)
|
||||
const batchSynthesizing = ref(false)
|
||||
const isRefreshing = ref(false)
|
||||
const recipes = ref([])
|
||||
|
||||
@ -142,6 +157,21 @@ function getOverallProgress(recipe) {
|
||||
return Math.round((getReadyCount(recipe) / recipe.materials.length) * 100)
|
||||
}
|
||||
|
||||
function getMaxSynthesizeCount(recipe) {
|
||||
return Number(recipe?.max_synthesize_count || 0)
|
||||
}
|
||||
|
||||
function confirmSynthesis({ title, content }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.showModal({
|
||||
title,
|
||||
content,
|
||||
success: (res) => res.confirm ? resolve() : reject(new Error('cancel')),
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function loadRecipes() {
|
||||
loading.value = true
|
||||
const userId = uni.getStorageSync('user_id')
|
||||
@ -166,15 +196,11 @@ async function onRefresh() {
|
||||
}
|
||||
|
||||
async function onSynthesize(recipe) {
|
||||
if (synthesizing.value || !recipe.can_synthesize) return
|
||||
if (synthesizing.value || batchSynthesizing.value || !recipe.can_synthesize) return
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
uni.showModal({
|
||||
title: '确认合成',
|
||||
content: `确定要合成「${recipe.target_product?.name || '目标商品'}」吗?合成后碎片将被消耗。`,
|
||||
success: (res) => res.confirm ? resolve() : reject('cancel'),
|
||||
fail: reject
|
||||
})
|
||||
await confirmSynthesis({
|
||||
title: '确认合成',
|
||||
content: `确定要合成「${recipe.target_product?.name || '目标商品'}」吗?合成后碎片将被消耗。`
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
@ -193,6 +219,33 @@ async function onSynthesize(recipe) {
|
||||
}
|
||||
}
|
||||
|
||||
async function onBatchSynthesize(recipe) {
|
||||
const maxCount = getMaxSynthesizeCount(recipe)
|
||||
if (batchSynthesizing.value || synthesizing.value || maxCount <= 0) return
|
||||
|
||||
try {
|
||||
await confirmSynthesis({
|
||||
title: '确认一键合成',
|
||||
content: `将消耗当前全部可用碎片,预计合成 ${maxCount} 次「${recipe.target_product?.name || '目标商品'}」,是否继续?`
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
batchSynthesizing.value = true
|
||||
const userId = uni.getStorageSync('user_id')
|
||||
try {
|
||||
const res = await doBatchSynthesis(userId, recipe.id)
|
||||
const count = Number(res?.synthesized_count || maxCount)
|
||||
uni.showToast({ title: `一键合成成功,共合成 ${count} 次`, icon: 'none' })
|
||||
await loadRecipes()
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e?.message || '一键合成失败', icon: 'none' })
|
||||
} finally {
|
||||
batchSynthesizing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
loadRecipes()
|
||||
})
|
||||
@ -535,27 +588,47 @@ defineExpose({ onShow })
|
||||
/* 底部行:进度提示 + 按钮 */
|
||||
.ticket-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 14rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.ready-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.ready-hint {
|
||||
font-size: 20rpx;
|
||||
color: $text-tertiary;
|
||||
}
|
||||
|
||||
.batch-hint {
|
||||
font-size: 20rpx;
|
||||
color: $brand-primary;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.action-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
/* 合成按钮 - 小胶囊 */
|
||||
.synth-btn {
|
||||
height: 56rpx;
|
||||
padding: 0 28rpx;
|
||||
border-radius: 28rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 52rpx;
|
||||
padding: 0 16rpx;
|
||||
border-radius: 26rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s cubic-bezier(0.18, 0.89, 0.32, 1.28);
|
||||
|
||||
&.btn-ready {
|
||||
@ -563,7 +636,7 @@ defineExpose({ onShow })
|
||||
box-shadow: 0 6rpx 16rpx rgba($brand-primary, 0.3);
|
||||
|
||||
&:active {
|
||||
transform: scale(0.94);
|
||||
transform: scale(0.96);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
@ -573,15 +646,25 @@ defineExpose({ onShow })
|
||||
}
|
||||
}
|
||||
|
||||
.synth-btn-primary {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.synth-btn-secondary {
|
||||
&.btn-ready {
|
||||
background: linear-gradient(135deg, rgba($brand-primary, 0.14), rgba($brand-primary, 0.08));
|
||||
box-shadow: none;
|
||||
border: 1.5rpx solid rgba($brand-primary, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 24rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1rpx;
|
||||
letter-spacing: 0;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
.btn-ready & { color: #fff; }
|
||||
.btn-locked & { color: $text-tertiary; }
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-shine {
|
||||
|
||||
@ -213,16 +213,30 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="ticket-footer">
|
||||
<text class="ready-hint">
|
||||
{{ getSynthReadyCount(recipe) }}/{{ recipe.materials?.length || 0 }} 材料就绪
|
||||
</text>
|
||||
<view
|
||||
class="synth-btn"
|
||||
:class="recipe.can_synthesize ? 'btn-ready' : 'btn-locked'"
|
||||
@tap="onSynthesize(recipe)"
|
||||
>
|
||||
<text class="btn-text">{{ synthesizing ? '合成中' : (recipe.can_synthesize ? '合成' : '不足') }}</text>
|
||||
<view v-if="recipe.can_synthesize" class="btn-shine"></view>
|
||||
<view class="ready-meta">
|
||||
<text class="ready-hint">
|
||||
{{ getSynthReadyCount(recipe) }}/{{ recipe.materials?.length || 0 }} 材料就绪
|
||||
</text>
|
||||
<text class="batch-hint" v-if="getSynthMaxCount(recipe) > 0">
|
||||
最多可合成 {{ getSynthMaxCount(recipe) }} 次
|
||||
</text>
|
||||
</view>
|
||||
<view class="action-group">
|
||||
<view
|
||||
class="synth-btn synth-btn-secondary"
|
||||
:class="recipe.can_synthesize && !batchSynthesizing ? 'btn-ready' : 'btn-locked'"
|
||||
@tap="onSynthesize(recipe)"
|
||||
>
|
||||
<text class="btn-text">{{ synthesizing ? '合成中' : (recipe.can_synthesize ? '单次合成' : '不足') }}</text>
|
||||
</view>
|
||||
<view
|
||||
class="synth-btn synth-btn-primary"
|
||||
:class="getSynthMaxCount(recipe) > 0 && !synthesizing ? 'btn-ready' : 'btn-locked'"
|
||||
@tap="onBatchSynthesize(recipe)"
|
||||
>
|
||||
<text class="btn-text">{{ batchSynthesizing ? '批量中' : (getSynthMaxCount(recipe) > 0 ? '一键合成' : '不足') }}</text>
|
||||
<view v-if="getSynthMaxCount(recipe) > 0 && !batchSynthesizing" class="btn-shine"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -294,8 +308,8 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onShow, onReachBottom, onShareAppMessage, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { getInventory, getProductDetail, redeemInventory, requestShipping, cancelShipping, listAddresses, getShipments, createAddressShare, createShippingFeeOrder } from '@/api/appUser'
|
||||
import { getSynthesisRecipes, doSynthesis } from '@/api/synthesis.js'
|
||||
import { getInventory, getProductDetail, redeemInventory, requestShipping, cancelShipping, listAddresses, getShipments, createAddressShare, checkShippingFee, createShippingFeeOrder } from '@/api/appUser'
|
||||
import { getSynthesisRecipes, doSynthesis, doBatchSynthesis } from '@/api/synthesis.js'
|
||||
import { vibrateShort } from '@/utils/vibrate.js'
|
||||
import { checkPhoneBoundSync } from '@/utils/checkPhone.js'
|
||||
import { executePaymentFlow } from '@/utils/payment.js'
|
||||
@ -326,6 +340,7 @@ const pendingShipIds = ref([])
|
||||
const recipes = ref([])
|
||||
const synthLoading = ref(false)
|
||||
const synthesizing = ref(false)
|
||||
const batchSynthesizing = ref(false)
|
||||
|
||||
const totalCount = computed(() => {
|
||||
return aggregatedList.value.reduce((sum, item) => sum + (item.count || 1), 0)
|
||||
@ -763,6 +778,21 @@ function getSynthReadyCount(recipe) {
|
||||
return recipe.materials.filter(m => m.owned_count >= m.required_count).length
|
||||
}
|
||||
|
||||
function getSynthMaxCount(recipe) {
|
||||
return Number(recipe?.max_synthesize_count || 0)
|
||||
}
|
||||
|
||||
function confirmSynthesisAction({ title, content }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.showModal({
|
||||
title,
|
||||
content,
|
||||
success: (res) => res.confirm ? resolve() : reject(new Error('cancel')),
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function loadRecipes(uid) {
|
||||
synthLoading.value = true
|
||||
const userId = uid || uni.getStorageSync('user_id')
|
||||
@ -778,23 +808,21 @@ async function loadRecipes(uid) {
|
||||
}
|
||||
|
||||
async function onSynthesize(recipe) {
|
||||
if (synthesizing.value || !recipe.can_synthesize) return
|
||||
if (synthesizing.value || batchSynthesizing.value || !recipe.can_synthesize) return
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
uni.showModal({
|
||||
title: '确认合成',
|
||||
content: `确定要合成「${recipe.target_product?.name || '目标商品'}」吗?合成后碎片将被消耗。`,
|
||||
success: (res) => res.confirm ? resolve() : reject('cancel'),
|
||||
fail: reject
|
||||
})
|
||||
await confirmSynthesisAction({
|
||||
title: '确认合成',
|
||||
content: `确定要合成「${recipe.target_product?.name || '目标商品'}」吗?合成后碎片将被消耗。`
|
||||
})
|
||||
} catch { return }
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
synthesizing.value = true
|
||||
const userId = uni.getStorageSync('user_id')
|
||||
try {
|
||||
await doSynthesis(userId, recipe.id)
|
||||
uni.showToast({ title: '合成成功!', icon: 'success' })
|
||||
await loadRecipes(userId)
|
||||
await Promise.all([loadRecipes(userId), loadInventory(userId)])
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e?.message || '合成失败', icon: 'none' })
|
||||
} finally {
|
||||
@ -802,6 +830,32 @@ async function onSynthesize(recipe) {
|
||||
}
|
||||
}
|
||||
|
||||
async function onBatchSynthesize(recipe) {
|
||||
const maxCount = getSynthMaxCount(recipe)
|
||||
if (batchSynthesizing.value || synthesizing.value || maxCount <= 0) return
|
||||
try {
|
||||
await confirmSynthesisAction({
|
||||
title: '确认一键合成',
|
||||
content: `将消耗当前全部可用碎片,预计合成 ${maxCount} 次「${recipe.target_product?.name || '目标商品'}」,是否继续?`
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
batchSynthesizing.value = true
|
||||
const userId = uni.getStorageSync('user_id')
|
||||
try {
|
||||
const res = await doBatchSynthesis(userId, recipe.id)
|
||||
const count = Number(res?.synthesized_count || maxCount)
|
||||
uni.showToast({ title: `一键合成成功,共合成 ${count} 次`, icon: 'none' })
|
||||
await Promise.all([loadRecipes(userId), loadInventory(userId)])
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e?.message || '一键合成失败', icon: 'none' })
|
||||
} finally {
|
||||
batchSynthesizing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onRedeem() {
|
||||
vibrateShort()
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
@ -907,13 +961,26 @@ async function confirmShipWithAddress() {
|
||||
showAddressPicker.value = false
|
||||
|
||||
const FREIGHT_THRESHOLD = 5
|
||||
const FREIGHT_FEE = 10
|
||||
|
||||
if (allIds.length < FREIGHT_THRESHOLD) {
|
||||
let shippingCheck
|
||||
try {
|
||||
shippingCheck = await checkShippingFee(user_id, allIds)
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e?.message || '运费校验失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
if (shippingCheck?.need_fee) {
|
||||
const fee = Number(shippingCheck?.fee_cents || 0) / 100
|
||||
const reason = shippingCheck?.reason
|
||||
const content = reason === 'contains_non_free_shipping_item'
|
||||
? `所选商品包含不包邮商品,需支付 ¥${fee.toFixed(2)} 运费,确认继续?`
|
||||
: `共 ${allIds.length} 件商品,不满 ${FREIGHT_THRESHOLD} 件需支付 ¥${fee.toFixed(2)} 运费,确认继续?`
|
||||
|
||||
const confirmed = await new Promise((resolve) => {
|
||||
uni.showModal({
|
||||
title: '需支付运费',
|
||||
content: `共 ${allIds.length} 件商品,不满 ${FREIGHT_THRESHOLD} 件需支付 ¥${FREIGHT_FEE}.00 运费,确认继续?`,
|
||||
content,
|
||||
confirmText: '去支付',
|
||||
cancelText: '取消',
|
||||
success: (res) => resolve(res.confirm)
|
||||
@ -934,25 +1001,8 @@ async function confirmShipWithAddress() {
|
||||
return
|
||||
}
|
||||
uni.hideLoading()
|
||||
|
||||
uni.showLoading({ title: '提交中...' })
|
||||
try {
|
||||
await requestShipping(user_id, allIds, addressId)
|
||||
uni.showToast({ title: '申请成功', icon: 'success' })
|
||||
pendingShipIds.value = []
|
||||
aggregatedList.value = []
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
loadInventory(user_id)
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e.message || '申请失败', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 满5件包邮直接发货
|
||||
uni.showLoading({ title: '提交中...' })
|
||||
try {
|
||||
await requestShipping(user_id, allIds, addressId)
|
||||
@ -1977,33 +2027,54 @@ function onCopyShareLink() {
|
||||
|
||||
.ticket-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 14rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.ready-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.ready-hint {
|
||||
font-size: 20rpx;
|
||||
color: $text-tertiary;
|
||||
}
|
||||
|
||||
.batch-hint {
|
||||
font-size: 20rpx;
|
||||
color: $brand-primary;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.action-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.synth-btn {
|
||||
height: 56rpx;
|
||||
padding: 0 28rpx;
|
||||
border-radius: 28rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 52rpx;
|
||||
padding: 0 16rpx;
|
||||
border-radius: 26rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s cubic-bezier(0.18, 0.89, 0.32, 1.28);
|
||||
|
||||
&.btn-ready {
|
||||
background: $gradient-brand;
|
||||
box-shadow: 0 6rpx 16rpx rgba($brand-primary, 0.3);
|
||||
|
||||
&:active {
|
||||
transform: scale(0.94);
|
||||
transform: scale(0.96);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
@ -2013,12 +2084,25 @@ function onCopyShareLink() {
|
||||
}
|
||||
}
|
||||
|
||||
.synth-btn-primary {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.synth-btn-secondary {
|
||||
&.btn-ready {
|
||||
background: linear-gradient(135deg, rgba($brand-primary, 0.14), rgba($brand-primary, 0.08));
|
||||
box-shadow: none;
|
||||
border: 1.5rpx solid rgba($brand-primary, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 24rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1rpx;
|
||||
letter-spacing: 0;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
white-space: nowrap;
|
||||
|
||||
.btn-ready & { color: #fff; }
|
||||
.btn-locked & { color: $text-tertiary; }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user