bindbox-mini/pages/shop/index.vue

883 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<view class="bg-decoration"></view>
<!-- 自定义 tabBar -->
<!-- #ifdef MP-TOUTIAO -->
<customTabBarToutiao />
<!-- #endif -->
<!-- #ifndef MP-TOUTIAO -->
<customTabBar />
<!-- #endif -->
<!-- 顶部固定区域 -->
<view class="header-section">
<view class="search-box">
<view class="search-input-wrap">
<text class="search-icon">🔍</text>
<input class="search-input" v-model="keyword" placeholder="搜索心仪的宝贝" placeholder-class="placeholder-style" confirm-type="search" @confirm="onSearchConfirm" />
</view>
</view>
<!-- 价格筛选 -->
<view class="filter-section">
<view class="filter-row">
<text class="filter-label">价格区间</text>
<view class="price-inputs">
<input
class="price-input"
type="number"
v-model="priceMin"
placeholder="最低"
placeholder-class="input-placeholder"
/>
<text class="price-separator">-</text>
<input
class="price-input"
type="number"
v-model="priceMax"
placeholder="最高"
placeholder-class="input-placeholder"
/>
</view>
<view class="filter-btn" @tap="applyPriceFilter">筛选</view>
<view class="filter-btn reset" @tap="resetPriceFilter" v-if="priceMin || priceMax">重置</view>
</view>
<view class="quick-prices">
<view
v-for="range in priceRanges"
:key="range.key"
class="quick-price-tag"
:class="{ active: isRangeActive(range) }"
@tap="selectQuickPrice(range)"
>
{{ range.label }}
</view>
</view>
</view>
<!-- 分类标签 -->
<view class="tab-row">
<view
v-for="tab in tabs"
:key="tab.id"
class="tab-item"
:class="{ active: currentTab === tab.id }"
@tap="switchTab(tab.id)"
>
<text class="tab-text">{{ tab.name }}</text>
<view class="tab-line" v-if="currentTab === tab.id"></view>
</view>
</view>
</view>
<!-- 占位 -->
<view class="header-placeholder"></view>
<view v-if="loading && !items.length" class="loading-container">
<view class="loading-animation">
<view class="circle circle-1"></view>
<view class="circle circle-2"></view>
<view class="circle circle-3"></view>
<view class="circle circle-4"></view>
</view>
<text class="loading-text">{{ loadingText }}</text>
</view>
<view class="content-container" v-else>
<!-- 商品视图 (网格) -->
<view v-if="currentTab === 'product'" class="products-grid">
<view class="product-item" v-for="p in items" :key="p.id" @tap="onProductTap(p)">
<view class="product-card">
<view class="thumb-wrap">
<image class="product-thumb" :src="p.image" mode="aspectFill" lazy-load="true" />
<view class="stock-tag" v-if="p.stock !== null && p.stock < 10 && p.stock > 0">仅剩{{p.stock}}</view>
<view class="stock-tag out" v-else-if="p.stock === 0">售罄</view>
</view>
<view class="product-info">
<text class="product-title">{{ p.title }}</text>
<view class="product-bottom">
<view class="price-row">
<text class="points-val">{{ p.points || 0 }}</text>
<text class="points-unit">积分</text>
</view>
<view
class="redeem-btn"
:class="{ disabled: p.stock === 0 }"
@tap.stop="onRedeemTap(p)"
>
{{ p.stock === 0 ? '已售罄' : '兑换' }}
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 优惠券视图 (票据样式列表) -->
<view v-else-if="currentTab === 'coupon'" class="coupons-list">
<view class="coupon-card" v-for="c in items" :key="c.id">
<view class="coupon-left">
<view class="coupon-val-row">
<text class="coupon-symbol">¥</text>
<text class="coupon-val">{{ (c.discount_value || 0) / 100 }}</text>
</view>
<text class="coupon-limit" v-if="c.min_spend > 0">{{ (c.min_spend || 0) / 100 }}可用</text>
<text class="coupon-limit" v-else>无门槛</text>
</view>
<view class="coupon-divider">
<view class="notch top"></view>
<view class="dash"></view>
<view class="notch bottom"></view>
</view>
<view class="coupon-right">
<view class="coupon-name">{{ c.title || c.name }}</view>
<view class="coupon-bottom">
<view class="coupon-price">
<text class="p-val">{{ c.points || 0 }}</text>
<text class="p-unit">积分</text>
</view>
<view class="coupon-btn" @tap="onRedeemTap(c)">立即兑换</view>
</view>
</view>
</view>
</view>
<!-- 道具卡视图 (列表卡片) -->
<view v-else-if="currentTab === 'item_card'" class="item-cards-list">
<!-- TODO: 后续我们要打开的 -->
<view class="empty-state" style="padding-top: 50rpx;">
<image class="empty-img" src="/static/empty.png" mode="widthFix" />
<text class="empty-text">暂未开放</text>
</view>
</view>
<view v-if="!items.length && currentTab !== 'item_card'" class="empty-state">
<image class="empty-img" src="/static/empty.png" mode="widthFix" />
<text class="empty-text">暂无相关兑换项</text>
</view>
<!-- 加载更多 -->
<view v-if="loading && items.length > 0" class="loading-more">
<view class="spinner"></view>
<text>{{ loadingText }}</text>
</view>
<view v-else-if="!hasMore && items.length > 0" class="no-more">- 到底啦 -</view>
</view>
</view>
</template>
<script setup>
import { onShow, onReachBottom } from '@dcloudio/uni-app'
import { ref, watch, onUnmounted } from 'vue'
import { getStoreItems, redeemProductByPoints, redeemCouponByPoints, redeemItemCardByPoints } from '../../api/appUser'
import { checkPhoneBound, checkPhoneBoundSync } from '../../utils/checkPhone.js'
// #ifdef MP-TOUTIAO
import customTabBarToutiao from '@/components/app-tab-bar-toutiao.vue'
// #endif
// #ifndef MP-TOUTIAO
import customTabBar from '@/components/app-tab-bar.vue'
// #endif
// 由于是 setup 语法,组件会自动注册,无需手动声明
const loading = ref(false)
const keyword = ref('')
const currentTab = ref('product')
const items = ref([])
const allItems = ref([])
const page = ref(1)
const pageSize = 20
const hasMore = ref(true)
// 趣味加载文字数组
const loadingTexts = [
'请稍等,正在努力加载中。。。',
'精彩马上就来,请稍候片刻~',
'正在搜寻更多好物,别急哦~',
'加载中,好东西值得等待~',
'正在为您准备惊喜,马上就好~',
'小憩一下,精彩内容即将呈现~',
'努力加载中,比心❤️~',
'正在赶来,马上就到~',
'稍安勿躁,美好值得等待~',
'加载进度99%... 开个玩笑😄'
]
const loadingText = ref(loadingTexts[0])
let loadingTextInterval = null
// 开始切换加载文字
function startLoadingTextRotation() {
if (loadingTextInterval) return
let index = 0
loadingText.value = loadingTexts[index]
loadingTextInterval = setInterval(() => {
index = (index + 1) % loadingTexts.length
loadingText.value = loadingTexts[index]
}, 2000) // 每2秒切换一次
}
// 停止切换加载文字
function stopLoadingTextRotation() {
if (loadingTextInterval) {
clearInterval(loadingTextInterval)
loadingTextInterval = null
}
}
// 监听 loading 状态,自动启停文字轮播
watch(loading, (newVal) => {
if (newVal) {
startLoadingTextRotation()
} else {
stopLoadingTextRotation()
}
})
// 价格筛选相关
const priceMin = ref('')
const priceMax = ref('')
const priceRanges = [
{ key: 'all', label: '全部', min: null, max: null },
{ key: '0-100', label: '0-100', min: 0, max: 100 },
{ key: '100-500', label: '100-500', min: 100, max: 500 },
{ key: '500-1000', label: '500-1K', min: 500, max: 1000 },
{ key: '1000-5000', label: '1K-5K', min: 1000, max: 5000 },
{ key: '5000+', label: '5K+', min: 5000, max: null }
]
const activePriceRange = ref('all')
const tabs = [
{ id: 'product', name: '商品' },
{ id: 'coupon', name: '优惠券' }
// { id: 'item_card', name: '道具卡' } // 暂不开放
]
function cleanUrl(u) {
const s = String(u || '').trim()
const m = s.match(/https?:\/\/[^\s'"`]+/)
if (m && m[0]) return m[0]
return s.replace(/[`'\"]/g, '').trim()
}
function normalizeItems(list, kind) {
if (!Array.isArray(list)) return []
// 格式化积分显示 - 不四舍五入,保留两位小数
const formatPoints = (value) => {
if (value === undefined || value === null) return '0.00'
const num = Number(value)
if (isNaN(num)) return '0.00'
// 价格字段单位是分,如 1250 = 12.50积分
// 除以100得到显示值
const finalValue = num / 100
// 使用 Math.floor 避免四舍五入,保留两位小数
return String(Math.floor(finalValue * 100) / 100).replace(/(\.\d)$/, '$10')
}
return list.map((i, idx) => ({
id: i.id,
kind: i.kind || kind,
image: cleanUrl(i.main_image || i.image || ''),
title: i.name || i.title || '',
price: i.price || i.discount_value || 0,
points: formatPoints(i.price || i.discount_value),
stock: i.in_stock ? 99 : 0, // Simplified stock check if returned as bool
discount_value: i.discount_value || 0,
min_spend: i.min_spend || 0,
description: i.description || ''
})).filter(i => i.title)
}
function switchTab(id) {
if (currentTab.value === id) return
currentTab.value = id
items.value = []
allItems.value = []
page.value = 1
hasMore.value = true
loadItems()
}
async function loadItems(append = false) {
if (loading.value) return
loading.value = true
try {
// 构建筛选参数
const filters = {}
if (keyword.value && keyword.value.trim()) {
filters.keyword = keyword.value.trim()
}
if (priceMin.value !== '' && priceMin.value !== null) {
filters.price_min = priceMin.value
}
if (priceMax.value !== '' && priceMax.value !== null) {
filters.price_max = priceMax.value
}
const res = await getStoreItems(currentTab.value, page.value, pageSize, filters)
const list = res.list || res || []
const total = res.total || 0
const newItems = normalizeItems(list, currentTab.value)
if (append) {
allItems.value = [...allItems.value, ...newItems]
} else {
allItems.value = newItems
}
// 直接使用服务端返回的数据(已经过筛选)
items.value = allItems.value
// 检查是否还有更多数据
const totalPages = Math.ceil(total / pageSize)
hasMore.value = page.value < totalPages
} catch (e) {
console.error(e)
hasMore.value = false
} finally {
loading.value = false
}
}
function applyFilters() {
const k = String(keyword.value || '').trim().toLowerCase()
const minPrice = priceMin.value !== '' ? parseFloat(priceMin.value) : null
const maxPrice = priceMax.value !== '' ? parseFloat(priceMax.value) : null
items.value = allItems.value.filter(item => {
// 关键词筛选
const title = String(item.title || '').toLowerCase()
const matchKeyword = !k || title.includes(k)
// 价格筛选
const itemPrice = item.points || 0
let matchPrice = true
if (minPrice !== null) {
matchPrice = matchPrice && itemPrice >= minPrice
}
if (maxPrice !== null) {
matchPrice = matchPrice && itemPrice <= maxPrice
}
return matchKeyword && matchPrice
})
}
function applyPriceFilter() {
activePriceRange.value = 'custom'
// 重新加载数据(服务端筛选)
page.value = 1
hasMore.value = true
allItems.value = []
loadItems()
}
function resetPriceFilter() {
priceMin.value = ''
priceMax.value = ''
activePriceRange.value = 'all'
// 重新加载数据
page.value = 1
hasMore.value = true
allItems.value = []
loadItems()
}
function selectQuickPrice(range) {
activePriceRange.value = range.key
if (range.min !== null) {
priceMin.value = range.min.toString()
} else {
priceMin.value = ''
}
if (range.max !== null) {
priceMax.value = range.max.toString()
} else {
priceMax.value = ''
}
// 重新加载数据
page.value = 1
hasMore.value = true
allItems.value = []
loadItems()
}
function isRangeActive(range) {
return activePriceRange.value === range.key
}
function onSearchConfirm() {
// 搜索时重新加载数据
page.value = 1
hasMore.value = true
allItems.value = []
loadItems()
}
function onProductTap(p) {
if (p.kind === 'product') {
uni.navigateTo({ url: `/pages-shop/shop/detail?id=${p.id}` })
}
}
async function onRedeemTap(item) {
// 检查商品库存
if (item.kind === 'product' && item.stock === 0) {
uni.showModal({
title: '商品已售罄',
content: '该商品库存不足请联系客服处理',
showCancel: false
})
return
}
const token = uni.getStorageSync('token')
if (!token) {
uni.showModal({
title: '提示',
content: '请先登录',
confirmText: '去登录',
success: (res) => { if (res.confirm) uni.navigateTo({ url: '/pages/login/index' }) }
})
return
}
uni.showModal({
title: '确认兑换',
content: `是否消耗 ${item.points} 积分兑换 ${item.title}`,
success: async (res) => {
if (res.confirm) {
uni.showLoading({ title: '兑换中...' })
try {
const userId = uni.getStorageSync('user_id')
if (!userId) throw new Error('用户ID不存在')
if (item.kind === 'product') {
await redeemProductByPoints(userId, item.id, 1)
} else if (item.kind === 'coupon') {
await redeemCouponByPoints(userId, item.id)
} else if (item.kind === 'item_card') {
await redeemItemCardByPoints(userId, item.id, 1)
}
uni.hideLoading()
uni.showModal({
title: '兑换成功',
content: `您已成功兑换 ${item.title || item.name}`,
showCancel: false,
success: () => {
loadItems()
}
})
} catch (e) {
uni.hideLoading()
uni.showToast({ title: e.message || '兑换失败', icon: 'none' })
}
}
}
})
}
onShow(() => {
// 检查手机号绑定状态(快速检查本地缓存)
if (!checkPhoneBoundSync()) return
const token = uni.getStorageSync('token')
if (token) {
page.value = 1
hasMore.value = true
allItems.value = []
loadItems()
}
})
onReachBottom(() => {
// 触底加载更多
if (hasMore.value && !loading.value) {
page.value++
loadItems(true)
}
})
watch(keyword, () => applyFilters())
// 组件卸载时清理定时器
onUnmounted(() => {
stopLoadingTextRotation()
})
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background-color: $bg-page;
padding-bottom: 40rpx;
position: relative;
overflow-x: hidden;
}
/* 顶部 Header */
.header-section {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 100;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(20rpx);
padding: 20rpx 24rpx 0;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.05);
}
.header-placeholder { height: 340rpx; }
.search-box { margin-bottom: 20rpx; }
.search-input-wrap {
display: flex; align-items: center;
background: rgba(0, 0, 0, 0.04);
border-radius: $radius-round;
padding: 16rpx 24rpx;
}
.search-icon { font-size: 28rpx; margin-right: 16rpx; opacity: 0.5; }
.search-input { flex: 1; font-size: 26rpx; color: $text-main; }
.placeholder-style { color: $text-tertiary; }
/* 价格筛选 */
.filter-section {
margin-bottom: 16rpx;
padding: 16rpx;
background: rgba(255, 255, 255, 0.6);
border-radius: $radius-md;
}
.filter-row {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.filter-label {
font-size: 24rpx;
color: $text-secondary;
white-space: nowrap;
margin-right: 12rpx;
}
.price-inputs {
flex: 1;
display: flex;
align-items: center;
gap: 8rpx;
}
.price-input {
flex: 1;
height: 56rpx;
background: rgba(0, 0, 0, 0.04);
border-radius: $radius-sm;
padding: 0 16rpx;
font-size: 24rpx;
color: $text-main;
text-align: center;
}
.input-placeholder { color: $text-tertiary; }
.price-separator {
font-size: 24rpx;
color: $text-tertiary;
padding: 0 4rpx;
}
.filter-btn {
padding: 12rpx 20rpx;
background: $gradient-brand;
color: #fff;
font-size: 24rpx;
border-radius: $radius-round;
margin-left: 12rpx;
white-space: nowrap;
&.reset {
background: rgba(0, 0, 0, 0.06);
color: $text-secondary;
}
}
.quick-prices {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.quick-price-tag {
padding: 8rpx 20rpx;
background: rgba(0, 0, 0, 0.04);
border-radius: $radius-round;
font-size: 22rpx;
color: $text-secondary;
transition: all 0.2s;
&.active {
background: $gradient-brand;
color: #fff;
font-weight: 600;
}
}
/* Tabs */
.tab-row {
display: flex;
justify-content: space-around;
padding-bottom: 4rpx;
}
.tab-item {
position: relative;
padding: 16rpx 20rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.tab-text {
font-size: 28rpx;
color: $text-secondary;
font-weight: 500;
transition: all 0.2s;
}
.tab-item.active .tab-text {
color: $brand-primary;
font-weight: 700;
font-size: 30rpx;
}
.tab-line {
position: absolute;
bottom: 0;
width: 40rpx;
height: 6rpx;
background: $gradient-brand;
border-radius: 4rpx;
}
.content-container { padding: 24rpx; }
/* 商品 Grid */
.products-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.product-card {
background: #fff;
border-radius: $radius-lg;
overflow: hidden;
box-shadow: $shadow-sm;
}
.thumb-wrap {
position: relative; width: 100%; padding-top: 100%;
background: #f8f8f8;
}
.product-thumb { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
.stock-tag {
position: absolute; bottom: 0; right: 0;
background: rgba($brand-primary, 0.9);
color: #fff; font-size: 20rpx; padding: 4rpx 12rpx;
border-top-left-radius: 12rpx;
&.out { background: #999; }
}
.product-info { padding: 16rpx; }
.product-title {
font-size: 26rpx; color: $text-main; font-weight: 600;
line-height: 1.4; height: 2.8em; overflow: hidden;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
margin-bottom: 12rpx;
}
.product-bottom { display: flex; align-items: center; justify-content: space-between; }
.points-val { font-size: 32rpx; font-weight: 800; color: #FF9800; font-family: 'DIN-Bold'; }
.points-unit { font-size: 20rpx; color: #FF9800; margin-left: 2rpx; }
.redeem-btn {
background: $gradient-brand; color: #fff; font-size: 22rpx;
padding: 6rpx 18rpx; border-radius: 24rpx; font-weight: 600;
&.disabled {
background: #ccc;
color: #999;
}
}
/* 优惠券 (Ticket Style) */
.coupons-list { display: flex; flex-direction: column; gap: 24rpx; }
.coupon-card {
display: flex;
background: #fff;
border-radius: $radius-lg;
height: 180rpx;
box-shadow: $shadow-sm;
overflow: hidden;
}
.coupon-left {
width: 200rpx;
background: linear-gradient(135deg, #FF6B6B, #FF3B30);
display: flex; flex-direction: column; align-items: center; justify-content: center;
color: #fff;
}
.coupon-val-row { display: flex; align-items: baseline; }
.coupon-symbol { font-size: 24rpx; font-weight: 600; }
.coupon-val { font-size: 56rpx; font-weight: 800; font-family: 'DIN-Bold'; }
.coupon-limit { font-size: 20rpx; opacity: 0.9; }
.coupon-divider {
width: 2rpx; position: relative;
background: #eee;
.notch {
position: absolute; width: 24rpx; height: 24rpx; background: $bg-page;
border-radius: 50%; left: -12rpx;
&.top { top: -12rpx; }
&.bottom { bottom: -12rpx; }
}
}
.coupon-right {
flex: 1; padding: 24rpx;
display: flex; flex-direction: column; justify-content: space-between;
}
.coupon-name { font-size: 30rpx; font-weight: 700; color: $text-main; }
.coupon-bottom { display: flex; align-items: center; justify-content: space-between; }
.coupon-price .p-val { font-size: 36rpx; font-weight: 800; color: #FF9800; }
.coupon-price .p-unit { font-size: 22rpx; color: #FF9800; }
.coupon-btn {
background: #333; color: #fff; font-size: 24rpx;
padding: 10rpx 24rpx; border-radius: 30rpx; font-weight: 600;
}
/* 道具卡 (Card Style) */
.item-cards-list { display: flex; flex-direction: column; gap: 24rpx; }
.item-card {
display: flex; background: #fff; border-radius: $radius-lg; padding: 24rpx;
box-shadow: $shadow-sm; align-items: center;
}
.ic-icon-wrap {
width: 100rpx; height: 100rpx; background: #f0f0f0;
border-radius: $radius-md; display: flex; align-items: center; justify-content: center;
font-size: 48rpx; margin-right: 24rpx;
}
.ic-info { flex: 1; display: flex; flex-direction: column; }
.ic-name { font-size: 30rpx; font-weight: 700; color: $text-main; margin-bottom: 4rpx; }
.ic-desc { font-size: 22rpx; color: $text-tertiary; margin-bottom: 20rpx; }
.ic-bottom { display: flex; align-items: center; justify-content: space-between; }
.ic-price .p-val { font-size: 32rpx; font-weight: 800; color: #FF9800; }
.ic-btn {
background: $gradient-brand; color: #fff; font-size: 24rpx;
padding: 8rpx 24rpx; border-radius: 30rpx;
}
.empty-state {
display: flex; flex-direction: column; align-items: center; padding-top: 100rpx;
}
.empty-img { width: 240rpx; opacity: 0.6; }
.empty-text { color: $text-tertiary; font-size: 26rpx; margin-top: 20rpx; }
/* 酷炫加载动画 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 150rpx;
}
.loading-animation {
position: relative;
width: 160rpx;
height: 160rpx;
margin-bottom: 40rpx;
}
.circle {
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
border: 4rpx solid transparent;
}
.circle-1 {
border-top-color: #FF6B6B;
border-right-color: #FF6B6B;
animation: rotate 1.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
}
.circle-2 {
width: 80%;
height: 80%;
top: 10%;
left: 10%;
border-top-color: #4ECDC4;
border-left-color: #4ECDC4;
animation: rotate 2s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite reverse;
}
.circle-3 {
width: 60%;
height: 60%;
top: 20%;
left: 20%;
border-bottom-color: #FFE66D;
border-left-color: #FFE66D;
animation: rotate 2.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
}
.circle-4 {
width: 40%;
height: 40%;
top: 30%;
left: 30%;
border-top-color: #95E1D3;
border-bottom-color: #95E1D3;
animation: rotate 3s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite reverse;
}
@keyframes rotate {
0% {
transform: rotate(0deg) scale(1);
opacity: 1;
}
50% {
transform: rotate(180deg) scale(1.1);
opacity: 0.8;
}
100% {
transform: rotate(360deg) scale(1);
opacity: 1;
}
}
.loading-text {
font-size: 28rpx;
color: $text-secondary;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 0.6;
}
50% {
opacity: 1;
}
}
.loading-wrap { padding: 100rpx 0; display: flex; justify-content: center; }
.spinner {
width: 48rpx; height: 48rpx; border: 4rpx solid #eee; border-top-color: $brand-primary;
border-radius: 50%; animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* 加载更多 */
.loading-more {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx 0;
color: $text-tertiary;
font-size: 24rpx;
gap: 12rpx;
}
.no-more {
text-align: center;
padding: 40rpx 0;
color: $text-tertiary;
font-size: 24rpx;
opacity: 0.6;
}
</style>