Files
medical-mall/pages/mall/merchant/index.uvue
2026-03-20 17:30:30 +08:00

896 lines
32 KiB
Plaintext
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="merchant-container">
<scroll-view scroll-y class="main-scroll" :refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
<!-- 头部区域 -->
<view class="header">
<view class="header-bg"></view>
<view class="header-content">
<view class="shop-info">
<image :src="shopInfo.shop_logo || '/static/images/default-shop.png'" class="shop-logo" mode="aspectFill" @click="goToSettings" />
<view class="shop-details">
<text class="shop-name">{{ shopInfo.shop_name || '我的店铺' }}</text>
<view class="shop-meta">
<view class="meta-item">
<text class="meta-icon">⭐</text>
<text class="meta-value">{{ shopInfo.rating_avg || 5.0 }}</text>
</view>
<view class="meta-divider"></view>
<view class="meta-item">
<text class="meta-icon">📦</text>
<text class="meta-value">{{ shopInfo.total_sales || 0 }}销量</text>
</view>
</view>
</view>
<view class="header-actions">
<view class="action-btn" @click="goToMessages">
<text class="action-icon">🔔</text>
<view v-if="unreadCount > 0" class="action-badge"><text>{{ unreadCount > 99 ? '99+' : unreadCount }}</text></view>
</view>
<view class="action-btn" @click="goToSettings">
<text class="action-icon">⚙️</text>
</view>
</view>
</view>
</view>
</view>
<view class="content-area">
<!-- 今日数据卡片 -->
<view class="stats-card">
<view class="stats-header">
<view class="stats-title-row">
<text class="stats-title">📊 今日数据</text>
<text class="stats-date">{{ currentDate }}</text>
</view>
</view>
<view class="stats-grid">
<view class="stats-item">
<view class="stats-icon-wrap blue">
<text class="stats-icon">📋</text>
</view>
<text class="stats-value">{{ todayStats.orders || 0 }}</text>
<text class="stats-label">订单数</text>
</view>
<view class="stats-item">
<view class="stats-icon-wrap green">
<text class="stats-icon">💰</text>
</view>
<text class="stats-value">¥{{ formatNumber(todayStats.sales) }}</text>
<text class="stats-label">销售额</text>
</view>
<view class="stats-item">
<view class="stats-icon-wrap orange">
<text class="stats-icon">👥</text>
</view>
<text class="stats-value">{{ todayStats.visitors || 0 }}</text>
<text class="stats-label">访客数</text>
</view>
<view class="stats-item">
<view class="stats-icon-wrap purple">
<text class="stats-icon">📈</text>
</view>
<text class="stats-value">{{ todayStats.conversion || 0 }}%</text>
<text class="stats-label">转化率</text>
</view>
</view>
</view>
<!-- 待处理事项 -->
<view class="section-card">
<view class="section-header">
<text class="section-title">🔔 待处理事项</text>
</view>
<view class="pending-grid">
<view class="pending-item" @click="goToOrders('pending')">
<view class="pending-icon-wrap orange">
<text class="pending-icon">📦</text>
</view>
<view class="pending-info">
<text class="pending-count" v-if="pendingCounts.pending_shipment > 0">{{ pendingCounts.pending_shipment }}</text>
<text class="pending-text">待发货</text>
</view>
</view>
<view class="pending-item" @click="goToOrders('refund')">
<view class="pending-icon-wrap red">
<text class="pending-icon">↩️</text>
</view>
<view class="pending-info">
<text class="pending-count" v-if="pendingCounts.refund_requests > 0">{{ pendingCounts.refund_requests }}</text>
<text class="pending-text">退款</text>
</view>
</view>
<view class="pending-item" @click="goToInventory">
<view class="pending-icon-wrap yellow">
<text class="pending-icon">⚠️</text>
</view>
<view class="pending-info">
<text class="pending-count" v-if="pendingCounts.low_stock > 0">{{ pendingCounts.low_stock }}</text>
<text class="pending-text">库存预警</text>
</view>
</view>
<view class="pending-item" @click="goToReviews">
<view class="pending-icon-wrap blue">
<text class="pending-icon">💬</text>
</view>
<view class="pending-info">
<text class="pending-count" v-if="pendingCounts.pending_reviews > 0">{{ pendingCounts.pending_reviews }}</text>
<text class="pending-text">待回复</text>
</view>
</view>
</view>
</view>
<!-- 常用功能 -->
<view class="section-card">
<view class="section-header">
<text class="section-title">🚀 常用功能</text>
</view>
<view class="shortcuts-grid">
<view class="shortcut-item" @click="goToProducts('add')">
<view class="shortcut-icon-wrap gradient-blue">
<text class="shortcut-icon"></text>
</view>
<text class="shortcut-text">发布商品</text>
</view>
<view class="shortcut-item" @click="goToOrders('all')">
<view class="shortcut-icon-wrap gradient-orange">
<text class="shortcut-icon">📋</text>
</view>
<text class="shortcut-text">订单管理</text>
</view>
<view class="shortcut-item" @click="goToProducts('manage')">
<view class="shortcut-icon-wrap gradient-green">
<text class="shortcut-icon">📦</text>
</view>
<text class="shortcut-text">商品管理</text>
</view>
<view class="shortcut-item" @click="goToInventory">
<view class="shortcut-icon-wrap gradient-purple">
<text class="shortcut-icon">📊</text>
</view>
<text class="shortcut-text">库存管理</text>
</view>
<view class="shortcut-item" @click="goToPromotions">
<view class="shortcut-icon-wrap gradient-red">
<text class="shortcut-icon">🎯</text>
</view>
<text class="shortcut-text">营销活动</text>
</view>
<view class="shortcut-item" @click="goToStatistics">
<view class="shortcut-icon-wrap gradient-cyan">
<text class="shortcut-icon">📈</text>
</view>
<text class="shortcut-text">数据统计</text>
</view>
<view class="shortcut-item" @click="goToFinance">
<view class="shortcut-icon-wrap gradient-yellow">
<text class="shortcut-icon">💰</text>
</view>
<text class="shortcut-text">财务结算</text>
</view>
<view class="shortcut-item" @click="goToMembers">
<view class="shortcut-icon-wrap gradient-pink">
<text class="shortcut-icon">VIP</text>
</view>
<text class="shortcut-text">会员管理</text>
</view>
<view class="shortcut-item" @click="goToSettings">
<view class="shortcut-icon-wrap gradient-pink">
<text class="shortcut-icon">🏪</text>
</view>
<text class="shortcut-text">店铺设置</text>
</view>
</view>
</view>
<!-- 最新订单 -->
<view class="section-card">
<view class="section-header">
<text class="section-title">🛒 最新订单</text>
<text class="section-more" @click="goToOrders('all')">查看全部 </text>
</view>
<view v-if="recentOrders.length === 0" class="empty-orders">
<text class="empty-icon">📭</text>
<text class="empty-text">暂无订单</text>
<text class="empty-hint">有新订单时会在这里显示</text>
</view>
<view v-else class="orders-list">
<view v-for="order in recentOrders" :key="order.id" class="order-card" @click="goToOrderDetail(order.id)">
<view class="order-header">
<view class="order-no-wrap">
<text class="order-label">订单号</text>
<text class="order-no">{{ order.order_no }}</text>
</view>
<text class="order-status" :class="getOrderStatusClass(order.order_status)">{{ getOrderStatusText(order.order_status) }}</text>
</view>
<view class="order-goods">
<view v-for="item in order.items.slice(0, 3)" :key="item.id" class="goods-item">
<image :src="item.image_url || '/static/images/default-product.png'" class="goods-image" mode="aspectFill" />
<view class="goods-info">
<text class="goods-name">{{ item.product_name }}</text>
<view class="goods-bottom">
<text class="goods-spec" v-if="item.sku_name">{{ item.sku_name }}</text>
<text class="goods-qty">×{{ item.quantity }}</text>
</view>
</view>
<text class="goods-price">¥{{ item.price }}</text>
</view>
<view v-if="order.items.length > 3" class="goods-more">
<text>还有{{ order.items.length - 3 }}件商品</text>
</view>
</view>
<view class="order-footer">
<text class="order-time">{{ formatTime(order.created_at) }}</text>
<view class="order-amount-wrap">
<text class="amount-label">合计</text>
<text class="amount-value">¥{{ order.total_amount.toFixed(2) }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 底部安全区域 -->
<view class="safe-bottom"></view>
</view>
</scroll-view>
</view>
</template>
<script lang="uts">
import supa from '@/components/supadb/aksupainstance.uts'
type ShopInfoType = {
id: string | null
merchant_id: string | null
shop_name: string | null
shop_logo: string | null
shop_banner: string | null
description: string | null
contact_name: string | null
contact_phone: string | null
rating_avg: number | null
total_sales: number | null
status: number | null
}
type OrderItemType = {
id: string
order_id: string
product_id: string
sku_id: string
product_name: string
sku_name: string
price: number
quantity: number
image_url: string
sku_snapshot: string
}
type OrderType = {
id: string
order_no: string
order_status: number
total_amount: number
created_at: string
items: OrderItemType[]
}
type TodayStatsType = {
orders: number | null
sales: number | null
visitors: number | null
conversion: number | null
}
type PendingCountsType = {
pending_shipment: number | null
refund_requests: number | null
low_stock: number | null
pending_reviews: number | null
}
export default {
data() {
return {
merchantId: '',
shopInfo: {
id: null,
merchant_id: null,
shop_name: null,
shop_logo: null,
shop_banner: null,
description: null,
contact_name: null,
contact_phone: null,
rating_avg: null,
total_sales: null,
status: null
} as ShopInfoType,
todayStats: {
orders: null,
sales: null,
visitors: null,
conversion: null
} as TodayStatsType,
pendingCounts: {
pending_shipment: 0,
refund_requests: 0,
low_stock: 0,
pending_reviews: 0
} as PendingCountsType,
recentOrders: [] as OrderType[],
unreadCount: 0,
refreshing: false
}
},
computed: {
currentDate(): string {
const now = new Date()
return `${now.getMonth() + 1}月${now.getDate()}日`
}
},
onLoad() {
this.initMerchantId()
},
onShow() {
if (this.merchantId) {
this.loadAllData()
this.startRealtimeSubscription()
} else {
setTimeout(() => {
this.loadAllData()
this.startRealtimeSubscription()
}, 500)
}
},
onHide() {
this.stopRealtimeSubscription()
},
onUnload() {
this.stopRealtimeSubscription()
},
methods: {
async initMerchantId() {
try {
const session = supa.getSession()
if (session != null && session.user != null) {
this.merchantId = session.user.getString('id') || ''
}
if (!this.merchantId) {
this.merchantId = uni.getStorageSync('user_id') || ''
}
} catch (e) {
console.error('获取商户ID失败:', e)
}
},
startRealtimeSubscription() {
if (!this.merchantId) return
// 监听订单表的变化
try {
supa.channel('ml_orders_realtime')
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'ml_orders',
filter: `merchant_id=eq.${this.merchantId}`
}, (payload) => {
console.log('收到订单实时更新:', payload)
// 延迟一下再刷新,避免连续变动导致频繁请求
setTimeout(() => {
this.loadTodayStats()
this.loadPendingCounts()
this.loadRecentOrders()
}, 500)
})
.subscribe()
} catch (e) {
console.error('订阅实时更新失败:', e)
}
},
stopRealtimeSubscription() {
try {
supa.channel('ml_orders_realtime').unsubscribe()
} catch (e) {
console.error('取消订阅失败:', e)
}
},
async loadAllData() {
await this.loadMerchantData()
await this.loadTodayStats()
await this.loadPendingCounts()
await this.loadRecentOrders()
await this.loadUnreadCount()
},
formatNumber(value: number | null): string {
if (value == null) return '0.00'
return value.toFixed(2)
},
async loadMerchantData() {
try {
const response = await supa
.from('ml_shops')
.select('*')
.eq('merchant_id', this.merchantId)
.limit(1)
.execute()
if (response.error != null) { console.error('ml_shops请求500报错', response.error) }
if (response.error != null || !response.data || (response.data as any[]).length === 0) {
this.shopInfo = {
id: null,
merchant_id: this.merchantId,
shop_name: '我的店铺',
shop_logo: null,
shop_banner: null,
description: null,
contact_name: null,
contact_phone: null,
rating_avg: 5.0,
total_sales: 0,
status: 1
}
return
}
const rawData = (response.data as any[])[0] as UTSJSONObject
this.shopInfo = {
id: rawData.getString('id') || null,
merchant_id: rawData.getString('merchant_id') || null,
shop_name: rawData.getString('shop_name') || '我的店铺',
shop_logo: rawData.getString('shop_logo') || null,
shop_banner: rawData.getString('shop_banner') || null,
description: rawData.getString('description') || null,
contact_name: rawData.getString('contact_name') || null,
contact_phone: rawData.getString('contact_phone') || null,
rating_avg: rawData.getNumber('rating_avg') || 5.0,
total_sales: rawData.getNumber('total_sales') || 0,
status: rawData.getNumber('status') || 1
}
// 重新动态查询并计算该店铺下所有商品的真实销量总和
try {
const salesRes = await supa
.from('ml_products')
.select('sale_count')
.eq('merchant_id', this.merchantId)
.execute()
if (salesRes.error != null) { console.error('ml_products sale_count报错', salesRes.error) }
if (salesRes.data != null) {
let calcTotalSales: number = 0
const salesData = salesRes.data as any[]
for (let i = 0; i < salesData.length; i++) {
const productInfo = salesData[i] as UTSJSONObject
const currentSale = productInfo.getNumber('sale_count')
if (currentSale != null) {
calcTotalSales += currentSale
}
}
let baseSales: number = 0
if (this.shopInfo.total_sales != null) {
baseSales = Number(this.shopInfo.total_sales)
}
if (calcTotalSales > baseSales) {
this.shopInfo.total_sales = calcTotalSales
}
}
} catch (e) {
console.error('获取店铺真实销量失败:', e)
}
} catch (e) {
console.error('加载店铺信息失败:', e)
}
},
async loadTodayStats() {
try {
// 1. 获取所有订单
const response = await supa
.from('ml_orders')
.select(`
total_amount,
order_status,
created_at,
order_items (quantity)
`)
.eq('merchant_id', this.merchantId)
.execute()
if (response.error != null) { console.error('ml_orders stats报错', response.error); return }
let todayOrders = 0
let todaySales = 0
let allTimeSalesVolume = 0 // 总销量(件数)
const now = new Date()
// 获取今日0点的毫秒数 (本地时间)
const todayStartMs = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()
const rawData = response.data as any[]
if (rawData != null) {
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i] as UTSJSONObject
const status = item.getNumber('order_status')
// 有效订单(已支付、已发货、已完成) >= 2
// 如果是退款(0)或取消(5),可能不计入今日销售额,这里按需调整
if (status != null && status >= 2 && status < 5) {
// 计算总销量(即售出的商品总件数)
const itemsObj = item.get('order_items')
if (itemsObj != null && Array.isArray(itemsObj)) {
const itemsArr = itemsObj as any[]
for (let j = 0; j < itemsArr.length; j++) {
const orderItem = itemsArr[j] as UTSJSONObject
allTimeSalesVolume += Math.floor(orderItem.getNumber('quantity') || 1)
}
} else {
allTimeSalesVolume += 1
}
// 判断是否是今日数据
const createdAtStr = item.getString('created_at') || ''
if (createdAtStr.length > 0) {
const orderDateMs = new Date(createdAtStr).getTime()
if (orderDateMs >= todayStartMs) {
todayOrders++
todaySales += item.getNumber('total_amount') || 0
}
}
}
}
}
// 更新店铺总销量显示
let currentShopSales = Number(this.shopInfo.total_sales || 0)
if (allTimeSalesVolume > currentShopSales) {
this.shopInfo.total_sales = allTimeSalesVolume
}
this.todayStats = {
orders: todayOrders,
sales: todaySales,
visitors: Math.floor(todayOrders * (2.5 + Math.random())) + 5, // 模拟访客数
conversion: todayOrders > 0 ? (12 + Math.floor(Math.random() * 8)) : 0 // 模拟转化率
}
} catch (e) {
console.error('获取今日统计异常:', e)
}
},
async loadPendingCounts() {
try {
const pendingShipmentRes = await supa
.from('ml_orders')
.select('id', { count: 'exact' })
.eq('merchant_id', this.merchantId)
.eq('order_status', 2)
.execute()
if (pendingShipmentRes.error != null) { console.error('pendingShipment报错', pendingShipmentRes.error) }
const refundRes = await supa
.from('ml_orders')
.select('id', { count: 'exact' })
.eq('merchant_id', this.merchantId)
.eq('order_status', 0)
.execute()
if (refundRes.error != null) { console.error('refundRes报错', refundRes.error) }
const lowStockRes = await supa
.from('ml_products')
.select('id', { count: 'exact' })
.eq('merchant_id', this.merchantId)
.lte('total_stock', 10)
.execute()
if (lowStockRes.error != null) { console.error('lowStockRes报错', lowStockRes.error) }
this.pendingCounts = {
pending_shipment: pendingShipmentRes.total || 0,
refund_requests: refundRes.total || 0,
low_stock: lowStockRes.total || 0,
pending_reviews: 0
}
} catch (e) {
console.error('获取待处理数量异常:', e)
}
},
async loadRecentOrders() {
try {
const response = await supa
.from('ml_orders')
.select(`
*,
order_items (
id,
product_id,
product_name,
sku_name,
price,
quantity,
image_url
)
`)
.eq('merchant_id', this.merchantId)
.order('created_at', { ascending: false })
.limit(5)
.execute()
if (response.error != null) { console.error('recentOrders报错', response.error) }
if (response.error != null || !response.data) { this.recentOrders = []; return; }
const rawData = response.data as any[]
const ordersData: OrderType[] = []
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i] as UTSJSONObject
const order: OrderType = {
id: item.getString('id') || '',
order_no: item.getString('order_no') || '',
order_status: item.getNumber('order_status') || 1,
total_amount: item.getNumber('total_amount') || 0,
created_at: item.getString('created_at') || '',
items: []
}
const itemsObj = item.get('order_items')
if (itemsObj != null && Array.isArray(itemsObj)) {
const itemsArray = itemsObj as any[]
for (let j = 0; j < itemsArray.length; j++) {
const orderItem = itemsArray[j] as UTSJSONObject
order.items.push({
id: orderItem.getString('id') || '',
order_id: '',
product_id: orderItem.getString('product_id') || '',
sku_id: '',
product_name: orderItem.getString('product_name') || '',
sku_name: orderItem.getString('sku_name') || '',
price: orderItem.getNumber('price') || 0,
quantity: orderItem.getNumber('quantity') || 0,
image_url: orderItem.getString('image_url') || '',
sku_snapshot: ''
} as OrderItemType)
}
}
ordersData.push(order)
}
this.recentOrders = ordersData
} catch (e) {
console.error('加载最新订单异常:', e); uni.showModal({title: '最新订单报错', content: e.toString()})
}
},
async loadUnreadCount() {
try {
const response = await supa
.from('ml_chat_messages')
.select('id', { count: 'exact' })
.eq('receiver_id', this.merchantId)
.eq('is_read', false)
.execute()
if (response.error != null) { uni.showModal({title: 'ml_chat_messages报错', content: JSON.stringify(response.error)}) }
this.unreadCount = response.total || 0
} catch (e) {
console.error('获取未读消息数失败:', e)
}
},
onRefresh() {
this.refreshing = true
this.loadAllData().then(() => {
this.refreshing = false
})
},
getOrderStatusClass(status: number): string {
if (status === 1) return 'status-pending'
if (status === 2) return 'status-paid'
if (status === 3) return 'status-shipped'
if (status === 4) return 'status-completed'
if (status === 0) return 'status-refund'
return 'status-default'
},
getOrderStatusText(status: number): string {
if (status === 1) return '待付款'
if (status === 2) return '待发货'
if (status === 3) return '已发货'
if (status === 4) return '已完成'
if (status === 0) return '退款中'
return '未知'
},
formatTime(timeStr: string): string {
if (!timeStr) return ''
const date = new Date(timeStr)
const now = new Date()
const diff = now.getTime() - date.getTime()
const minutes = Math.floor(diff / (1000 * 60))
if (minutes < 60) return `${minutes}分钟前`
if (minutes < 1440) return `${Math.floor(minutes / 60)}小时前`
return `${date.getMonth() + 1}-${date.getDate()}`
},
goToMessages() {
uni.navigateTo({ url: '/pages/mall/merchant/messages' })
},
goToSettings() {
uni.navigateTo({ url: '/pages/mall/merchant/shop-edit' })
},
goToOrders(type: string) {
uni.navigateTo({ url: `/pages/mall/merchant/orders?type=${type}` })
},
goToProducts(type: string) {
if (type === 'add') {
uni.navigateTo({ url: '/pages/mall/merchant/product-edit' })
} else {
uni.navigateTo({ url: '/pages/mall/merchant/products' })
}
},
goToPromotions() {
uni.navigateTo({ url: '/pages/mall/merchant/promotions' })
},
goToStatistics() {
uni.navigateTo({ url: '/pages/mall/merchant/statistics' })
},
goToFinance() {
uni.navigateTo({ url: '/pages/mall/merchant/finance' })
},
goToReviews() {
uni.navigateTo({ url: '/pages/mall/merchant/reviews' })
},
goToInventory() {
uni.navigateTo({ url: '/pages/mall/merchant/inventory' })
},
goToMembers() {
uni.navigateTo({ url: '/pages/mall/merchant/members' })
},
goToOrderDetail(orderId: string) {
uni.navigateTo({ url: `/pages/mall/merchant/order-detail?id=${orderId}` })
}
}
}
</script>
<style>
.merchant-container { background-color: #f5f7fa; min-height: 100vh; }
.main-scroll { height: 100vh; }
.header { position: relative; padding-bottom: 30rpx; }
.header-bg { position: absolute; top: 0; left: 0; right: 0; height: 300rpx; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 0 0 40rpx 40rpx; }
.header-content { position: relative; padding: 40rpx 30rpx 0; }
.shop-info { display: flex; flex-direction: row; align-items: center; }
.shop-logo { width: 110rpx; height: 110rpx; border-radius: 20rpx; border-width: 4rpx; border-style: solid; border-color: rgba(255,255,255,0.8); margin-right: 24rpx; background-color: #fff; }
.shop-details { flex: 1; }
.shop-name { font-size: 40rpx; font-weight: bold; color: #fff; margin-bottom: 12rpx; }
.shop-meta { display: flex; flex-direction: row; align-items: center; }
.meta-item { display: flex; flex-direction: row; align-items: center; }
.meta-icon { font-size: 24rpx; margin-right: 6rpx; }
.meta-value { font-size: 26rpx; color: rgba(255,255,255,0.9); }
.meta-divider { width: 2rpx; height: 24rpx; background-color: rgba(255,255,255,0.3); margin-left: 20rpx; margin-right: 20rpx; }
.header-actions { display: flex; flex-direction: row; }
.action-btn { position: relative; margin-left: 24rpx; width: 72rpx; height: 72rpx; background-color: rgba(255,255,255,0.2); border-radius: 36rpx; display: flex; align-items: center; justify-content: center; }
.action-icon { font-size: 36rpx; }
.action-badge { position: absolute; top: -4rpx; right: -4rpx; min-width: 36rpx; height: 36rpx; background-color: #FF3B30; border-radius: 18rpx; display: flex; align-items: center; justify-content: center; padding-left: 8rpx; padding-right: 8rpx; border-width: 2rpx; border-style: solid; border-color: #fff; }
.action-badge-text { font-size: 20rpx; color: #fff; font-weight: bold; }
.content-area { padding-left: 24rpx; padding-right: 24rpx; padding-bottom: 30rpx; margin-top: 10rpx; }
.stats-card { background-color: #fff; border-radius: 24rpx; padding: 28rpx; margin-bottom: 24rpx; }
.stats-header { margin-bottom: 24rpx; }
.stats-title-row { display: flex; flex-direction: row; justify-content: space-between; align-items: center; }
.stats-title { font-size: 32rpx; font-weight: bold; color: #333; }
.stats-date { font-size: 24rpx; color: #999; background-color: #f5f7fa; padding-top: 6rpx; padding-bottom: 6rpx; padding-left: 16rpx; padding-right: 16rpx; border-radius: 12rpx; }
.stats-grid { display: flex; flex-direction: row; justify-content: space-between; }
.stats-item { width: 160rpx; display: flex; flex-direction: column; align-items: center; }
.stats-icon-wrap { width: 64rpx; height: 64rpx; border-radius: 16rpx; display: flex; align-items: center; justify-content: center; margin-bottom: 12rpx; }
.stats-icon-wrap.blue { background-color: #E3F2FD; }
.stats-icon-wrap.green { background-color: #E8F5E9; }
.stats-icon-wrap.orange { background-color: #FFF3E0; }
.stats-icon-wrap.purple { background-color: #F3E5F5; }
.stats-icon { font-size: 28rpx; }
.stats-value { font-size: 36rpx; font-weight: bold; color: #333; margin-bottom: 4rpx; }
.stats-label { font-size: 24rpx; color: #999; }
.section-card { background-color: #fff; border-radius: 24rpx; padding: 28rpx; margin-bottom: 24rpx; }
.section-header { display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin-bottom: 24rpx; }
.section-title { font-size: 32rpx; font-weight: bold; color: #333; }
.section-more { font-size: 26rpx; color: #007AFF; padding-top: 8rpx; padding-bottom: 8rpx; padding-left: 16rpx; padding-right: 16rpx; background-color: #E3F2FD; border-radius: 12rpx; }
.pending-grid { display: flex; flex-direction: row; justify-content: space-between; }
.pending-item { width: 160rpx; display: flex; flex-direction: column; align-items: center; padding-top: 16rpx; padding-bottom: 16rpx; }
.pending-icon-wrap { width: 88rpx; height: 88rpx; border-radius: 24rpx; display: flex; align-items: center; justify-content: center; margin-bottom: 12rpx; }
.pending-icon-wrap.orange { background-color: #FFF3E0; }
.pending-icon-wrap.red { background-color: #FFEBEE; }
.pending-icon-wrap.yellow { background-color: #FFFDE7; }
.pending-icon-wrap.blue { background-color: #E3F2FD; }
.pending-icon { font-size: 40rpx; }
.pending-info { display: flex; flex-direction: column; align-items: center; }
.pending-count { font-size: 32rpx; font-weight: bold; color: #FF6B35; margin-bottom: 4rpx; }
.pending-text { font-size: 24rpx; color: #666; }
.shortcuts-grid { display: flex; flex-direction: row; flex-wrap: wrap; }
.shortcut-item { width: 25%; display: flex; flex-direction: column; align-items: center; padding-top: 20rpx; padding-bottom: 20rpx; }
.shortcut-icon-wrap { width: 88rpx; height: 88rpx; border-radius: 24rpx; display: flex; align-items: center; justify-content: center; margin-bottom: 12rpx; }
.shortcut-icon-wrap.gradient-blue { background-color: #667eea; }
.shortcut-icon-wrap.gradient-orange { background-color: #f093fb; }
.shortcut-icon-wrap.gradient-green { background-color: #4facfe; }
.shortcut-icon-wrap.gradient-purple { background-color: #a18cd1; }
.shortcut-icon-wrap.gradient-red { background-color: #ff9a9e; }
.shortcut-icon-wrap.gradient-cyan { background-color: #a1c4fd; }
.shortcut-icon-wrap.gradient-yellow { background-color: #f6d365; }
.shortcut-icon-wrap.gradient-pink { background-color: #ffecd2; }
.shortcut-icon { font-size: 40rpx; }
.shortcut-text { font-size: 24rpx; color: #666; }
.empty-orders { display: flex; flex-direction: column; align-items: center; justify-content: center; padding-top: 80rpx; padding-bottom: 80rpx; }
.empty-icon { font-size: 100rpx; margin-bottom: 20rpx; }
.empty-text { font-size: 30rpx; color: #666; margin-bottom: 8rpx; }
.empty-hint { font-size: 24rpx; color: #999; }
.orders-list { display: flex; flex-direction: column; }
.order-card { background-color: #f9fafb; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; border-width: 1rpx; border-style: solid; border-color: #eee; }
.order-header { display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin-bottom: 20rpx; }
.order-no-wrap { display: flex; flex-direction: row; align-items: center; }
.order-label { font-size: 22rpx; color: #999; background-color: #eee; padding-top: 4rpx; padding-bottom: 4rpx; padding-left: 12rpx; padding-right: 12rpx; border-radius: 8rpx; margin-right: 12rpx; }
.order-no { font-size: 26rpx; color: #333; font-weight: 500; }
.order-status { font-size: 24rpx; padding-top: 8rpx; padding-bottom: 8rpx; padding-left: 20rpx; padding-right: 20rpx; border-radius: 20rpx; font-weight: 500; }
.status-pending { background-color: #FFF3E0; color: #FF9800; }
.status-paid { background-color: #E3F2FD; color: #2196F3; }
.status-shipped { background-color: #E8F5E9; color: #4CAF50; }
.status-completed { background-color: #F3E5F5; color: #9C27B0; }
.status-refund { background-color: #FFEBEE; color: #F44336; }
.order-goods { margin-bottom: 16rpx; }
.goods-item { display: flex; flex-direction: row; align-items: center; margin-bottom: 16rpx; background-color: #fff; padding: 16rpx; border-radius: 12rpx; }
.goods-image { width: 100rpx; height: 100rpx; border-radius: 12rpx; margin-right: 16rpx; background-color: #f5f5f5; }
.goods-info { flex: 1; }
.goods-name { font-size: 28rpx; color: #333; margin-bottom: 8rpx; font-weight: 500; }
.goods-bottom { display: flex; flex-direction: row; justify-content: space-between; align-items: center; }
.goods-spec { font-size: 22rpx; color: #999; }
.goods-qty { font-size: 24rpx; color: #999; }
.goods-price { font-size: 28rpx; color: #FF6B35; font-weight: bold; }
.goods-more { text-align: center; padding-top: 12rpx; padding-bottom: 12rpx; font-size: 24rpx; color: #999; background-color: #fff; border-radius: 12rpx; }
.order-footer { display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding-top: 16rpx; border-top-width: 1rpx; border-top-style: solid; border-top-color: #eee; }
.order-time { font-size: 24rpx; color: #999; }
.order-amount-wrap { display: flex; flex-direction: row; align-items: center; }
.amount-label { font-size: 24rpx; color: #999; margin-right: 8rpx; }
.amount-value { font-size: 32rpx; font-weight: bold; color: #FF6B35; }
.safe-bottom { height: 30rpx; }
</style>