feat(miniprogram): 温润东方风全面 UI 重设计
73 文件变更,覆盖全部 40 个页面 SCSS + TabBar 图标 + 组件样式。 统一赤陶主色 #C4623A + 暖米背景 + 衬线标题字体 + 12px 圆角体系。
This commit is contained in:
@@ -1,5 +1,35 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.detail-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
@@ -8,34 +38,34 @@
|
||||
|
||||
/* ===== 余额卡片 ===== */
|
||||
.balance-card {
|
||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||
background: $card;
|
||||
margin: 20px 24px 16px;
|
||||
border-radius: $r-lg;
|
||||
padding: 32px;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
.balance-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.balance-label {
|
||||
font-size: 26px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.balance-value {
|
||||
font-size: 56px;
|
||||
@include serif-number;
|
||||
font-size: 60px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
letter-spacing: 1px;
|
||||
color: $pri;
|
||||
display: block;
|
||||
margin-bottom: 28px;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
.balance-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
background: $bg;
|
||||
border-radius: $r;
|
||||
padding: 20px 0;
|
||||
}
|
||||
@@ -48,47 +78,46 @@
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
@include serif-number;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&.green {
|
||||
color: #A7F3D0;
|
||||
&.stat-earn {
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
&.orange {
|
||||
color: #FDE68A;
|
||||
&.stat-spend {
|
||||
color: $wrn;
|
||||
}
|
||||
|
||||
&.gray {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
&.stat-expired {
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 22px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 1px;
|
||||
height: 48px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: $bd;
|
||||
}
|
||||
|
||||
/* ===== 类型筛选标签 ===== */
|
||||
.type-tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
padding: 20px 24px 0;
|
||||
background: $card;
|
||||
padding: 0 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.type-tab {
|
||||
@include flex-center;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 16px 0;
|
||||
position: relative;
|
||||
|
||||
@@ -98,7 +127,7 @@
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 48px;
|
||||
width: 40px;
|
||||
height: 4px;
|
||||
background: $pri;
|
||||
border-radius: 2px;
|
||||
@@ -107,9 +136,9 @@
|
||||
|
||||
.type-tab-text {
|
||||
font-size: 28px;
|
||||
color: $tx2;
|
||||
color: $tx3;
|
||||
|
||||
&.active {
|
||||
.type-tab.active & {
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -127,45 +156,44 @@
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.tx-icon {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.tx-badge {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: $r;
|
||||
@include flex-center;
|
||||
margin-right: 20px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.type-earn {
|
||||
&.tx-badge-earn {
|
||||
background: $acc-l;
|
||||
}
|
||||
|
||||
&.type-spend {
|
||||
background: $dan-l;
|
||||
&.tx-badge-spend {
|
||||
background: $wrn-l;
|
||||
}
|
||||
|
||||
&.type-expired {
|
||||
&.tx-badge-expired {
|
||||
background: $bd-l;
|
||||
}
|
||||
}
|
||||
|
||||
.tx-icon-text {
|
||||
font-size: 32px;
|
||||
.tx-badge-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
|
||||
.type-earn & {
|
||||
.tx-badge-earn & {
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
.type-spend & {
|
||||
color: $dan;
|
||||
.tx-badge-spend & {
|
||||
color: $wrn;
|
||||
}
|
||||
|
||||
.type-expired & {
|
||||
.tx-badge-expired & {
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
@@ -200,20 +228,22 @@
|
||||
}
|
||||
|
||||
.tx-amount {
|
||||
@include serif-number;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&.positive {
|
||||
&.tx-amount-positive {
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
&.negative {
|
||||
color: $dan;
|
||||
&.tx-amount-negative {
|
||||
color: $tx2;
|
||||
}
|
||||
}
|
||||
|
||||
.tx-remaining {
|
||||
@include serif-number;
|
||||
font-size: 20px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
@@ -13,12 +13,6 @@ const TYPE_TABS = [
|
||||
{ key: 'spend', label: '支出' },
|
||||
];
|
||||
|
||||
const TYPE_ICONS: Record<string, { icon: string; className: string }> = {
|
||||
earn: { icon: '↑', className: 'type-earn' },
|
||||
spend: { icon: '↓', className: 'type-spend' },
|
||||
expired: { icon: '⏰', className: 'type-expired' },
|
||||
};
|
||||
|
||||
export default function PointsDetail() {
|
||||
const [account, setAccount] = useState<PointsAccount | null>(null);
|
||||
const [transactions, setTransactions] = useState<PointsTransaction[]>([]);
|
||||
@@ -48,7 +42,6 @@ export default function PointsDetail() {
|
||||
page_size: 10,
|
||||
});
|
||||
let list = res.data || [];
|
||||
// 前端按类型过滤(后端暂不支持 type 参数)
|
||||
if (type) {
|
||||
list = list.filter((t) => t.type === type);
|
||||
}
|
||||
@@ -99,8 +92,16 @@ export default function PointsDetail() {
|
||||
fetchTransactions(1, key, true);
|
||||
};
|
||||
|
||||
const getTypeConfig = (type: string) => {
|
||||
return TYPE_ICONS[type] || { icon: '?', className: 'type-earn' };
|
||||
const getTypeLabel = (type: string) => {
|
||||
if (type === 'earn') return '收';
|
||||
if (type === 'spend') return '支';
|
||||
return '过';
|
||||
};
|
||||
|
||||
const getTypeClass = (type: string) => {
|
||||
if (type === 'earn') return 'earn';
|
||||
if (type === 'spend') return 'spend';
|
||||
return 'expired';
|
||||
};
|
||||
|
||||
const formatAmount = (tx: PointsTransaction) => {
|
||||
@@ -122,23 +123,21 @@ export default function PointsDetail() {
|
||||
<View className='detail-page'>
|
||||
{/* 余额卡片 */}
|
||||
<View className='balance-card'>
|
||||
<View className='balance-row'>
|
||||
<Text className='balance-label'>当前积分</Text>
|
||||
<Text className='balance-value'>{balance.toLocaleString()}</Text>
|
||||
</View>
|
||||
<Text className='balance-label'>当前积分</Text>
|
||||
<Text className='balance-value'>{balance.toLocaleString()}</Text>
|
||||
<View className='balance-stats'>
|
||||
<View className='stat-item'>
|
||||
<Text className='stat-value green'>{(account?.total_earned ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-value stat-earn'>{(account?.total_earned ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-label'>累计获得</Text>
|
||||
</View>
|
||||
<View className='stat-divider' />
|
||||
<View className='stat-item'>
|
||||
<Text className='stat-value orange'>{(account?.total_spent ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-value stat-spend'>{(account?.total_spent ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-label'>累计消费</Text>
|
||||
</View>
|
||||
<View className='stat-divider' />
|
||||
<View className='stat-item'>
|
||||
<Text className='stat-value gray'>{(account?.total_expired ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-value stat-expired'>{(account?.total_expired ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-label'>已过期</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -152,26 +151,21 @@ export default function PointsDetail() {
|
||||
className={`type-tab ${activeTab === tab.key ? 'active' : ''}`}
|
||||
onClick={() => handleTabChange(tab.key)}
|
||||
>
|
||||
<Text
|
||||
className={`type-tab-text ${activeTab === tab.key ? 'active' : ''}`}
|
||||
>
|
||||
{tab.label}
|
||||
</Text>
|
||||
<Text className='type-tab-text'>{tab.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 交易列表 */}
|
||||
{transactions.length === 0 && !loading ? (
|
||||
<EmptyState icon='📊' text='暂无积分记录' hint='签到或兑换后将显示记录' />
|
||||
<EmptyState icon='' text='暂无积分记录' hint='签到或兑换后将显示记录' />
|
||||
) : (
|
||||
<View className='transaction-list'>
|
||||
{transactions.map((tx) => {
|
||||
const typeCfg = getTypeConfig(tx.type);
|
||||
return (
|
||||
<View className='transaction-item' key={tx.id}>
|
||||
<View className={`tx-icon ${typeCfg.className}`}>
|
||||
<Text className='tx-icon-text'>{typeCfg.icon}</Text>
|
||||
<View className={`tx-badge tx-badge-${getTypeClass(tx.type)}`}>
|
||||
<Text className='tx-badge-text'>{getTypeLabel(tx.type)}</Text>
|
||||
</View>
|
||||
<View className='tx-info'>
|
||||
<Text className='tx-desc'>
|
||||
@@ -180,7 +174,7 @@ export default function PointsDetail() {
|
||||
<Text className='tx-date'>{formatDate(tx.created_at)}</Text>
|
||||
</View>
|
||||
<View className='tx-amount-col'>
|
||||
<Text className={`tx-amount ${tx.type === 'earn' ? 'positive' : 'negative'}`}>
|
||||
<Text className={`tx-amount tx-amount-${tx.type === 'earn' ? 'positive' : 'negative'}`}>
|
||||
{formatAmount(tx)}
|
||||
</Text>
|
||||
<Text className='tx-remaining'>余额 {tx.balance_after.toLocaleString()}</Text>
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.exchange-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
@@ -7,58 +37,69 @@
|
||||
}
|
||||
|
||||
/* ===== 商品预览 ===== */
|
||||
.product-preview {
|
||||
.product-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32px 24px;
|
||||
background: $card;
|
||||
margin-bottom: 16px;
|
||||
margin: 20px 24px 16px;
|
||||
border-radius: $r-lg;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
.product-icon-wrap {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
border-radius: $r;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@include flex-center;
|
||||
margin-right: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.preview-icon {
|
||||
font-size: 64px;
|
||||
.product-icon-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 52px;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.preview-info {
|
||||
.product-meta {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.preview-name {
|
||||
.product-name {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.preview-type {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
.product-type-tag {
|
||||
@include tag($pri-l, $pri-d);
|
||||
}
|
||||
|
||||
/* ===== 兑换详情 ===== */
|
||||
.exchange-detail {
|
||||
background: $card;
|
||||
/* ===== 兑换明细 ===== */
|
||||
.detail-section {
|
||||
padding: 0 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.detail-section-title {
|
||||
@include section-title;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
box-shadow: $shadow-sm;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -66,7 +107,7 @@
|
||||
padding: 24px 0;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:last-child {
|
||||
&.last {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
@@ -77,35 +118,37 @@
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
@include serif-number;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
font-weight: bold;
|
||||
|
||||
&.cost {
|
||||
color: $wrn;
|
||||
font-size: 32px;
|
||||
&.detail-cost {
|
||||
color: $pri;
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
&.sufficient {
|
||||
&.detail-sufficient {
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
&.insufficient {
|
||||
&.detail-insufficient {
|
||||
color: $dan;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 温馨提示 ===== */
|
||||
.exchange-notice {
|
||||
.notice-section {
|
||||
background: $card;
|
||||
padding: 24px;
|
||||
margin: 0 24px;
|
||||
border-radius: $r;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
@include section-title;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@@ -113,7 +156,7 @@
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
line-height: 1.6;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
@@ -128,7 +171,7 @@
|
||||
padding: 16px 24px;
|
||||
padding-bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
background: $card;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0 -2px 12px rgba(45, 42, 38, 0.06);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@@ -143,31 +186,28 @@
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.footer-cost-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.footer-cost-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.footer-cost-num {
|
||||
font-size: 36px;
|
||||
@include serif-number;
|
||||
font-size: 38px;
|
||||
font-weight: bold;
|
||||
color: $wrn;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
.footer-cost-unit {
|
||||
font-size: 22px;
|
||||
color: $tx2;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background: $pri;
|
||||
padding: 20px 48px;
|
||||
border-radius: $r;
|
||||
border-radius: $r-pill;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&.disabled {
|
||||
background: $tx3;
|
||||
opacity: 0.6;
|
||||
background: $bd;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,22 @@ import type { PointsAccount, PointsProduct } from '../../../services/points';
|
||||
import Loading from '../../../components/Loading';
|
||||
import './index.scss';
|
||||
|
||||
const TYPE_ICONS: Record<string, string> = {
|
||||
physical: '📦',
|
||||
service: '🎫',
|
||||
privilege: '👑',
|
||||
const TYPE_INITIAL: Record<string, string> = {
|
||||
physical: '物',
|
||||
service: '券',
|
||||
privilege: '权',
|
||||
};
|
||||
|
||||
const TYPE_LABEL: Record<string, string> = {
|
||||
physical: '实物商品',
|
||||
service: '服务券',
|
||||
privilege: '权益卡',
|
||||
};
|
||||
|
||||
const TYPE_COLOR: Record<string, string> = {
|
||||
physical: '#5B7A5E',
|
||||
service: '#C4623A',
|
||||
privilege: '#8B3E1F',
|
||||
};
|
||||
|
||||
export default function ExchangeConfirm() {
|
||||
@@ -81,7 +93,6 @@ export default function ExchangeConfirm() {
|
||||
const order = await exchangeProduct(product.id);
|
||||
Taro.showToast({ title: '兑换成功', icon: 'success', duration: 2000 });
|
||||
|
||||
// 展示核销码弹窗
|
||||
setTimeout(() => {
|
||||
Taro.showModal({
|
||||
title: '兑换成功',
|
||||
@@ -115,62 +126,62 @@ export default function ExchangeConfirm() {
|
||||
);
|
||||
}
|
||||
|
||||
const productType = product?.product_type || 'physical';
|
||||
const initial = TYPE_INITIAL[productType] || '礼';
|
||||
const typeLabel = TYPE_LABEL[productType] || '商品';
|
||||
const typeColor = TYPE_COLOR[productType] || '#C4623A';
|
||||
|
||||
return (
|
||||
<View className='exchange-page'>
|
||||
{/* 商品信息卡片 */}
|
||||
<View className='product-preview'>
|
||||
{/* 商品预览卡片 */}
|
||||
<View className='product-card'>
|
||||
<View
|
||||
className='preview-image'
|
||||
style={{ backgroundColor: '#0891B2' }}
|
||||
className='product-icon-wrap'
|
||||
style={{ backgroundColor: typeColor }}
|
||||
>
|
||||
<Text className='preview-icon'>
|
||||
{product ? TYPE_ICONS[product.product_type] || '🎁' : '🎁'}
|
||||
</Text>
|
||||
<Text className='product-icon-char'>{initial}</Text>
|
||||
</View>
|
||||
<View className='preview-info'>
|
||||
<Text className='preview-name'>{product?.name || ''}</Text>
|
||||
<Text className='preview-type'>
|
||||
{product?.product_type === 'physical'
|
||||
? '实物商品'
|
||||
: product?.product_type === 'service'
|
||||
? '服务券'
|
||||
: '权益卡'}
|
||||
</Text>
|
||||
<View className='product-meta'>
|
||||
<Text className='product-name'>{product?.name || ''}</Text>
|
||||
<Text className='product-type-tag'>{typeLabel}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 兑换详情 */}
|
||||
<View className='exchange-detail'>
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>所需积分</Text>
|
||||
<Text className='detail-value cost'>{cost.toLocaleString()}</Text>
|
||||
</View>
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>当前余额</Text>
|
||||
<Text
|
||||
className={`detail-value ${insufficient ? 'insufficient' : 'sufficient'}`}
|
||||
>
|
||||
{balance.toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
{insufficient && (
|
||||
{/* 兑换明细 */}
|
||||
<View className='detail-section'>
|
||||
<Text className='detail-section-title'>兑换明细</Text>
|
||||
<View className='detail-card'>
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>差额</Text>
|
||||
<Text className='detail-value insufficient'>
|
||||
-{(cost - balance).toLocaleString()}
|
||||
<Text className='detail-label'>所需积分</Text>
|
||||
<Text className='detail-value detail-cost'>{cost.toLocaleString()}</Text>
|
||||
</View>
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>当前余额</Text>
|
||||
<Text
|
||||
className={`detail-value ${insufficient ? 'detail-insufficient' : 'detail-sufficient'}`}
|
||||
>
|
||||
{balance.toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
{insufficient && (
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>差额</Text>
|
||||
<Text className='detail-value detail-insufficient'>
|
||||
-{(cost - balance).toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<View className='detail-row last'>
|
||||
<Text className='detail-label'>库存</Text>
|
||||
<Text className='detail-value'>
|
||||
{product && product.stock > 0 ? `剩余 ${product.stock} 件` : '已兑完'}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>库存</Text>
|
||||
<Text className='detail-value'>
|
||||
{product && product.stock > 0 ? `剩余 ${product.stock} 件` : '已兑完'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 温馨提示 */}
|
||||
<View className='exchange-notice'>
|
||||
<View className='notice-section'>
|
||||
<Text className='notice-title'>温馨提示</Text>
|
||||
<Text className='notice-text'>
|
||||
兑换成功后将生成核销码,请凭核销码到前台核销领取。
|
||||
@@ -182,10 +193,8 @@ export default function ExchangeConfirm() {
|
||||
<View className='exchange-footer'>
|
||||
<View className='footer-cost'>
|
||||
<Text className='footer-cost-label'>合计</Text>
|
||||
<View className='footer-cost-value'>
|
||||
<Text className='footer-cost-icon'>🪙</Text>
|
||||
<Text className='footer-cost-num'>{cost.toLocaleString()}</Text>
|
||||
</View>
|
||||
<Text className='footer-cost-num'>{cost.toLocaleString()}</Text>
|
||||
<Text className='footer-cost-unit'>积分</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`confirm-btn ${insufficient || (product?.stock ?? 0) <= 0 || submitting ? 'disabled' : ''}`}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/mixins.scss';
|
||||
|
||||
.mall-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding-bottom: 40px;
|
||||
padding-bottom: calc(120px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* ===== 积分余额卡片 ===== */
|
||||
/* ─── 积分余额卡片 ─── */
|
||||
.mall-header {
|
||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||
padding: 32px;
|
||||
padding-top: 48px;
|
||||
padding: 48px 32px 36px;
|
||||
}
|
||||
|
||||
.points-card {
|
||||
@@ -20,7 +20,7 @@
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.points-card-top {
|
||||
.points-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -36,9 +36,13 @@
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
padding: 10px 28px;
|
||||
border-radius: 32px;
|
||||
border-radius: $r-pill;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&.checked {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
@@ -47,8 +51,8 @@
|
||||
|
||||
.checkin-btn-text {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.checkin-btn.checked .checkin-btn-text {
|
||||
@@ -56,12 +60,14 @@
|
||||
}
|
||||
|
||||
.points-balance {
|
||||
@include serif-number;
|
||||
font-size: 72px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
color: #fff;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 2px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.points-streak {
|
||||
@@ -70,12 +76,10 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ===== 商品类型切换 ===== */
|
||||
/* ─── 商品类型切换 ─── */
|
||||
.type-tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
padding: 20px 24px 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.type-tab {
|
||||
@@ -103,15 +107,15 @@
|
||||
|
||||
&.active {
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 商品网格 ===== */
|
||||
/* ─── 商品网格 ─── */
|
||||
.product-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
gap: 16px;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
@@ -119,19 +123,32 @@
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 200px;
|
||||
@include flex-center;
|
||||
|
||||
&.type-physical { background: $pri-l; }
|
||||
&.type-service { background: $acc-l; }
|
||||
&.type-privilege { background: $wrn-l; }
|
||||
}
|
||||
|
||||
.product-image-icon {
|
||||
font-size: 64px;
|
||||
.product-image-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 56px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
line-height: 1;
|
||||
|
||||
.type-service & { color: $acc; }
|
||||
.type-privilege & { color: $wrn; }
|
||||
}
|
||||
|
||||
.product-info {
|
||||
@@ -140,7 +157,7 @@
|
||||
|
||||
.product-name {
|
||||
font-size: 26px;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
@@ -161,11 +178,15 @@
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.product-points-icon {
|
||||
.product-points-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: $wrn;
|
||||
}
|
||||
|
||||
.product-points-value {
|
||||
@include serif-number;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $wrn;
|
||||
@@ -174,15 +195,69 @@
|
||||
.product-stock {
|
||||
font-size: 20px;
|
||||
padding: 2px 10px;
|
||||
border-radius: 8px;
|
||||
border-radius: $r-sm;
|
||||
|
||||
&.out {
|
||||
color: $tx3;
|
||||
background: $bd-l;
|
||||
@include tag($bd-l, $tx3);
|
||||
}
|
||||
|
||||
&.low {
|
||||
color: $dan;
|
||||
background: $dan-l;
|
||||
@include tag($dan-l, $dan);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── 空状态 ─── */
|
||||
.mall-empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 160px 40px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
background: $pri-l;
|
||||
@include flex-center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.empty-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 52px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: $tx;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 26px;
|
||||
color: $tx3;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.empty-action {
|
||||
background: $pri;
|
||||
border-radius: $r;
|
||||
padding: 16px 48px;
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-action-text {
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -9,21 +9,20 @@ import {
|
||||
} from '../../services/points';
|
||||
import type { PointsAccount, PointsProduct, CheckinStatus } from '../../services/points';
|
||||
import { useAuthStore } from '../../stores/auth';
|
||||
import EmptyState from '../../components/EmptyState';
|
||||
import Loading from '../../components/Loading';
|
||||
import './index.scss';
|
||||
|
||||
const PRODUCT_TYPE_TABS = [
|
||||
{ key: '', label: '全部' },
|
||||
{ key: 'physical', label: '实物' },
|
||||
{ key: 'service', label: '服务券' },
|
||||
{ key: 'privilege', label: '权益' },
|
||||
{ key: 'physical', label: '实物', char: '物' },
|
||||
{ key: 'service', label: '服务券', char: '券' },
|
||||
{ key: 'privilege', label: '权益', char: '权' },
|
||||
];
|
||||
|
||||
const TYPE_COLORS: Record<string, string> = {
|
||||
physical: '#0891B2',
|
||||
service: '#059669',
|
||||
privilege: '#D97706',
|
||||
const TYPE_BG: Record<string, string> = {
|
||||
physical: 'type-physical',
|
||||
service: 'type-service',
|
||||
privilege: 'type-privilege',
|
||||
};
|
||||
|
||||
export default function Mall() {
|
||||
@@ -117,14 +116,9 @@ export default function Mall() {
|
||||
try {
|
||||
const result = await dailyCheckin();
|
||||
setCheckinStatus(result);
|
||||
// 刷新积分余额
|
||||
const acct = await getAccount();
|
||||
setAccount(acct);
|
||||
Taro.showToast({
|
||||
title: '签到成功',
|
||||
icon: 'success',
|
||||
duration: 2000,
|
||||
});
|
||||
Taro.showToast({ title: '签到成功', icon: 'success', duration: 2000 });
|
||||
} catch (err) {
|
||||
Taro.showToast({
|
||||
title: err instanceof Error ? err.message : '签到失败',
|
||||
@@ -150,40 +144,36 @@ export default function Mall() {
|
||||
|
||||
const balance = account?.balance ?? 0;
|
||||
|
||||
if (noProfile) {
|
||||
return (
|
||||
<View className='mall-page'>
|
||||
<View className='mall-empty-state'>
|
||||
<View className='empty-icon'>
|
||||
<Text className='empty-char'>档</Text>
|
||||
</View>
|
||||
<Text className='empty-title'>请先完善个人档案</Text>
|
||||
<Text className='empty-hint'>建档后即可使用积分商城、签到等功能</Text>
|
||||
<View className='empty-action' onClick={() => Taro.navigateTo({ url: '/pages/profile/family-add/index' })}>
|
||||
<Text className='empty-action-text'>去建档</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='mall-page'>
|
||||
{/* 未关联患者档案时显示引导 */}
|
||||
{noProfile && (
|
||||
<View className='mall-page'>
|
||||
<EmptyState
|
||||
icon='👤'
|
||||
text='请先完善个人档案'
|
||||
hint='建档后即可使用积分商城、签到等功能'
|
||||
actionText='去建档'
|
||||
onAction={() => Taro.navigateTo({ url: '/pages/profile/family-add/index' })}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{!noProfile && (
|
||||
<>
|
||||
{/* 积分余额卡片 */}
|
||||
<View className='mall-header'>
|
||||
<View className='points-card'>
|
||||
<View className='points-card-top'>
|
||||
<View className='points-top'>
|
||||
<Text className='points-label'>当前积分</Text>
|
||||
<View
|
||||
className={`checkin-btn ${
|
||||
checkinStatus?.checked_in_today ? 'checked' : ''
|
||||
}`}
|
||||
className={`checkin-btn ${checkinStatus?.checked_in_today ? 'checked' : ''}`}
|
||||
onClick={handleCheckin}
|
||||
>
|
||||
<Text className='checkin-btn-text'>
|
||||
{checkinLoading
|
||||
? '...'
|
||||
: checkinStatus?.checked_in_today
|
||||
? '已签到'
|
||||
: '签到'}
|
||||
{checkinLoading ? '...' : checkinStatus?.checked_in_today ? '已签到' : '签到'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -204,11 +194,7 @@ export default function Mall() {
|
||||
className={`type-tab ${productType === tab.key ? 'active' : ''}`}
|
||||
onClick={() => handleTabChange(tab.key)}
|
||||
>
|
||||
<Text
|
||||
className={`type-tab-text ${
|
||||
productType === tab.key ? 'active' : ''
|
||||
}`}
|
||||
>
|
||||
<Text className={`type-tab-text ${productType === tab.key ? 'active' : ''}`}>
|
||||
{tab.label}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -217,35 +203,28 @@ export default function Mall() {
|
||||
|
||||
{/* 商品列表 */}
|
||||
{products.length === 0 && !loading ? (
|
||||
<EmptyState
|
||||
icon='🎁'
|
||||
text='暂无商品'
|
||||
hint='更多好物即将上架'
|
||||
/>
|
||||
<View className='mall-empty-state'>
|
||||
<View className='empty-icon'>
|
||||
<Text className='empty-char'>礼</Text>
|
||||
</View>
|
||||
<Text className='empty-title'>暂无商品</Text>
|
||||
<Text className='empty-hint'>更多好物即将上架</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className='product-grid'>
|
||||
{products.map((item) => (
|
||||
<View className='product-card' key={item.id} onClick={() => handleProductClick(item)}>
|
||||
<View
|
||||
className='product-image'
|
||||
style={{ backgroundColor: TYPE_COLORS[item.product_type] || '#94A3B8' }}
|
||||
>
|
||||
<Text className='product-image-icon'>
|
||||
{item.product_type === 'physical'
|
||||
? '📦'
|
||||
: item.product_type === 'service'
|
||||
? '🎫'
|
||||
: '👑'}
|
||||
<View className={`product-image ${TYPE_BG[item.product_type] || ''}`}>
|
||||
<Text className='product-image-char'>
|
||||
{item.product_type === 'physical' ? '物' : item.product_type === 'service' ? '券' : '权'}
|
||||
</Text>
|
||||
</View>
|
||||
<View className='product-info'>
|
||||
<Text className='product-name'>{item.name}</Text>
|
||||
<View className='product-bottom'>
|
||||
<View className='product-points'>
|
||||
<Text className='product-points-icon'>🪙</Text>
|
||||
<Text className='product-points-value'>
|
||||
{item.points_cost}
|
||||
</Text>
|
||||
<Text className='product-points-char'>P</Text>
|
||||
<Text className='product-points-value'>{item.points_cost}</Text>
|
||||
</View>
|
||||
{item.stock <= 0 ? (
|
||||
<Text className='product-stock out'>已兑完</Text>
|
||||
@@ -262,8 +241,6 @@ export default function Mall() {
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.orders-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
@@ -13,11 +43,12 @@
|
||||
padding: 20px 24px 0;
|
||||
background: $card;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 0 0 $r-lg $r-lg;
|
||||
}
|
||||
|
||||
.status-tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
@include flex-center;
|
||||
padding: 16px 0;
|
||||
position: relative;
|
||||
|
||||
@@ -27,7 +58,7 @@
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 48px;
|
||||
width: 40px;
|
||||
height: 4px;
|
||||
background: $pri;
|
||||
border-radius: 2px;
|
||||
@@ -36,9 +67,9 @@
|
||||
|
||||
.status-tab-text {
|
||||
font-size: 28px;
|
||||
color: $tx2;
|
||||
color: $tx3;
|
||||
|
||||
&.active {
|
||||
.status-tab.active & {
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -54,7 +85,7 @@
|
||||
border-radius: $r;
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.order-header {
|
||||
@@ -66,6 +97,7 @@
|
||||
}
|
||||
|
||||
.order-product {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
@@ -75,43 +107,12 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.order-status {
|
||||
.order-status-tag {
|
||||
@include tag(transparent, $tx3);
|
||||
padding: 4px 16px;
|
||||
border-radius: 20px;
|
||||
border-radius: $r-pill;
|
||||
margin-left: 12px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.status-pending {
|
||||
background: $wrn-l;
|
||||
|
||||
.order-status-text {
|
||||
color: $wrn;
|
||||
}
|
||||
}
|
||||
|
||||
&.status-verified {
|
||||
background: $acc-l;
|
||||
|
||||
.order-status-text {
|
||||
color: $acc;
|
||||
}
|
||||
}
|
||||
|
||||
&.status-cancelled {
|
||||
background: $dan-l;
|
||||
|
||||
.order-status-text {
|
||||
color: $dan;
|
||||
}
|
||||
}
|
||||
|
||||
&.status-expired {
|
||||
background: $bd-l;
|
||||
|
||||
.order-status-text {
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.order-status-text {
|
||||
@@ -136,11 +137,12 @@
|
||||
}
|
||||
|
||||
.order-row-value {
|
||||
@include serif-number;
|
||||
font-size: 26px;
|
||||
color: $tx;
|
||||
|
||||
&.cost {
|
||||
color: $wrn;
|
||||
&.order-cost {
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
@@ -158,11 +160,13 @@
|
||||
.qrcode-label {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.qrcode-value {
|
||||
@include serif-number;
|
||||
font-size: 24px;
|
||||
color: $pri;
|
||||
color: $pri-d;
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -14,11 +14,11 @@ const STATUS_TABS = [
|
||||
{ key: 'expired', label: '已过期' },
|
||||
];
|
||||
|
||||
const STATUS_CONFIG: Record<string, { label: string; className: string }> = {
|
||||
pending: { label: '待核销', className: 'status-pending' },
|
||||
verified: { label: '已核销', className: 'status-verified' },
|
||||
cancelled: { label: '已取消', className: 'status-cancelled' },
|
||||
expired: { label: '已过期', className: 'status-expired' },
|
||||
const STATUS_CONFIG: Record<string, { label: string; tagBg: string; tagColor: string }> = {
|
||||
pending: { label: '待核销', tagBg: '#FFF3E0', tagColor: '#C4873A' },
|
||||
verified: { label: '已核销', tagBg: '#E8F0E8', tagColor: '#5B7A5E' },
|
||||
cancelled: { label: '已取消', tagBg: '#FDEAEA', tagColor: '#B54A4A' },
|
||||
expired: { label: '已过期', tagBg: '#F0EBE5', tagColor: '#A8A29E' },
|
||||
};
|
||||
|
||||
export default function MallOrders() {
|
||||
@@ -40,7 +40,6 @@ export default function MallOrders() {
|
||||
page_size: 10,
|
||||
});
|
||||
let list = res.data || [];
|
||||
// 前端按状态过滤(后端暂不支持 status 参数)
|
||||
if (status) {
|
||||
list = list.filter((o) => o.status === status);
|
||||
}
|
||||
@@ -101,7 +100,7 @@ export default function MallOrders() {
|
||||
};
|
||||
|
||||
const getStatusConfig = (status: string) => {
|
||||
return STATUS_CONFIG[status] || { label: status, className: 'status-pending' };
|
||||
return STATUS_CONFIG[status] || { label: status, tagBg: '#F0EBE5', tagColor: '#A8A29E' };
|
||||
};
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
@@ -120,11 +119,7 @@ export default function MallOrders() {
|
||||
className={`status-tab ${activeTab === tab.key ? 'active' : ''}`}
|
||||
onClick={() => handleTabChange(tab.key)}
|
||||
>
|
||||
<Text
|
||||
className={`status-tab-text ${activeTab === tab.key ? 'active' : ''}`}
|
||||
>
|
||||
{tab.label}
|
||||
</Text>
|
||||
<Text className='status-tab-text'>{tab.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
@@ -132,7 +127,7 @@ export default function MallOrders() {
|
||||
{/* 订单列表 */}
|
||||
{orders.length === 0 && !loading ? (
|
||||
<EmptyState
|
||||
icon='📋'
|
||||
icon=''
|
||||
text='暂无订单'
|
||||
hint='去商城兑换心仪商品吧'
|
||||
actionText='去商城'
|
||||
@@ -146,7 +141,10 @@ export default function MallOrders() {
|
||||
<View className='order-card' key={order.id}>
|
||||
<View className='order-header'>
|
||||
<Text className='order-product'>商品 {order.product_id.slice(0, 8)}</Text>
|
||||
<View className={`order-status ${statusCfg.className}`}>
|
||||
<View
|
||||
className='order-status-tag'
|
||||
style={{ background: statusCfg.tagBg, color: statusCfg.tagColor }}
|
||||
>
|
||||
<Text className='order-status-text'>{statusCfg.label}</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -154,8 +152,8 @@ export default function MallOrders() {
|
||||
<View className='order-body'>
|
||||
<View className='order-row'>
|
||||
<Text className='order-row-label'>消耗积分</Text>
|
||||
<Text className='order-row-value cost'>
|
||||
🪙 {order.points_cost.toLocaleString()}
|
||||
<Text className='order-row-value order-cost'>
|
||||
{order.points_cost.toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
<View className='order-row'>
|
||||
@@ -166,9 +164,9 @@ export default function MallOrders() {
|
||||
</View>
|
||||
{order.status === 'pending' && (
|
||||
<View className='order-qrcode' onClick={() => handleShowQrCode(order.qr_code)}>
|
||||
<Text className='qrcode-label'>核销码: </Text>
|
||||
<Text className='qrcode-label'>核销码</Text>
|
||||
<Text className='qrcode-value'>{order.qr_code}</Text>
|
||||
<Text className='qrcode-tap'>点击查看</Text>
|
||||
<Text className='qrcode-tap'>查看</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user