Files
medical-mall/pages/mall/consumer/orders.uvue

1149 lines
31 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.
<!-- pages/mall/consumer/orders.uvue -->
<template>
<view class="orders-page">
<!-- 顶部标题栏 -->
<view class="orders-header">
<view class="header-search full-width">
<input
class="search-input"
type="text"
placeholder="搜索订单号或商品名称"
:value="searchKeyword"
@input="onSearchInput"
@confirm="onSearchConfirm"
/>
<text v-if="searchKeyword" class="search-clear" @click="clearSearch">×</text>
<text v-else class="search-icon">🔍</text>
</view>
</view>
<!-- 订单状态筛选 -->
<view class="order-tabs">
<scroll-view scroll-x class="tab-scroll" :show-scrollbar="false">
<view class="tab-container">
<view
v-for="tab in orderTabs"
:key="tab.id"
:class="['tab-item', { active: activeTab === tab.id }]"
@click="switchTab(tab.id)"
>
<text class="tab-name">{{ tab.name }}</text>
<text v-if="tab.count > 0" class="tab-count">{{ tab.count }}</text>
<view v-if="activeTab === tab.id" class="active-indicator"></view>
</view>
</view>
</scroll-view>
</view>
<!-- 订单列表 -->
<scroll-view
scroll-y
class="orders-content"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="loadMore"
>
<!-- 空状态 -->
<view v-if="!loading && orders.length === 0" class="empty-orders">
<text class="empty-icon">📦</text>
<text class="empty-title">暂无订单</text>
<text class="empty-desc">去逛逛,发现心仪的商品</text>
<button class="go-shopping-btn" @click="goShopping">去逛逛</button>
</view>
<!-- 订单列表 -->
<view v-else class="order-list">
<view
v-for="order in orders"
:key="order.id"
class="order-card"
>
<!-- 订单头部 -->
<view class="order-header">
<text class="order-no">订单号:{{ order.order_no }}</text>
<text :class="['order-status', getStatusClass(order.status)]">
{{ getStatusText(order.status) }}
</text>
</view>
<!-- 订单商品 -->
<view class="order-products">
<view
v-for="product in order.products"
:key="product.id"
class="order-product"
@click="navigateToProduct(product)"
>
<image
class="product-image"
:src="product.image"
mode="aspectFill"
/>
<view class="product-info">
<text class="product-name">{{ product.name }}</text>
<text class="product-spec">{{ product.spec }}</text>
<view class="product-footer">
<text class="product-price">¥{{ product.price }}</text>
<text class="product-quantity">×{{ product.quantity }}</text>
</view>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="order-info">
<view class="info-row">
<text class="info-label">商品合计</text>
<text class="info-value">¥{{ order.product_amount }}</text>
</view>
<view class="info-row">
<text class="info-label">运费</text>
<text class="info-value">¥{{ order.shipping_fee }}</text>
</view>
<view class="info-row total">
<text class="info-label">实付款</text>
<text class="info-value total-price">¥{{ order.total_amount }}</text>
</view>
</view>
<!-- 订单操作 -->
<view class="order-actions">
<view v-if="order.status === 1" class="action-buttons">
<button class="action-btn cancel" @click="cancelOrder(order.id)">取消订单</button>
<button class="action-btn pay" @click="payOrder(order.id)">立即支付</button>
</view>
<view v-if="order.status === 2" class="action-buttons">
<button class="action-btn remind" @click="remindShipping(order.id)">提醒发货</button>
<button class="action-btn refund" @click.stop="onApplyRefund(order)">申请售后</button>
</view>
<view v-if="order.status === 3" class="action-buttons">
<button class="action-btn view" @click="viewLogistics(order.id)">查看物流</button>
<button class="action-btn confirm" @click="confirmReceipt(order.id)">确认收货</button>
<button class="action-btn refund" @click.stop="onApplyRefund(order)">申请售后</button>
</view>
<view v-if="order.status === 4" class="action-buttons">
<button class="action-btn review" @click="goReview(order)">评价</button>
<button class="action-btn refund" @click.stop="onApplyRefund(order)">申请售后</button>
<button class="action-btn repurchase" @click="repurchase(order)">再次购买</button>
</view>
<view v-if="order.status === 5" class="action-buttons">
<button class="action-btn view" @click="viewOrderDetail(order.id)">查看详情</button>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<view v-if="loadingMore" class="loading-more">
<view class="loading-spinner"></view>
<text>加载中...</text>
</view>
<view v-if="!hasMore && orders.length > 0" class="no-more">
<text>没有更多订单了</text>
</view>
<!-- 安全区域 -->
<view class="safe-area"></view>
</scroll-view>
<!-- 底部导航 -->
<view class="tabbar-placeholder"></view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, onMounted, computed } from 'vue'
import { onShow, onLoad, onBackPress } from '@dcloudio/uni-app'
import { supabaseService } from '@/utils/supabaseService.uts'
// 拦截返回事件,避免跳回登录页
onBackPress((options) => {
if (options.from === 'navigateBack') {
const pages = getCurrentPages()
if (pages.length > 1) {
const prevPage = pages[pages.length - 2]
// 如果上一页是登录页,则重定向到个人中心
if (prevPage.route.includes('login')) {
uni.redirectTo({
url: '/pages/mall/consumer/profile'
})
return true
}
}
}
return false
})
// 定义标签页类型
type OrderTabItem = {
id: string,
name: string,
count: number
}
// 定义订单产品类型
type OrderProduct = {
id: string,
name: string,
price: number,
image: string,
spec: string,
quantity: number
}
// 定义订单类型
type OrderItem = {
id: string,
order_no: string,
status: number,
create_time: string,
product_amount: number,
shipping_fee: number,
total_amount: number,
products: OrderProduct[]
}
// 响应式数据
const orders = ref<OrderItem[]>([])
const allOrdersList = ref<OrderItem[]>([]) // Store all fetched orders for client-side filtering
const loading = ref<boolean>(false)
const loadingMore = ref<boolean>(false)
const hasMore = ref<boolean>(true)
const refreshing = ref<boolean>(false)
const page = ref<number>(1)
const activeTab = ref<string>('all')
const searchKeyword = ref<string>('')
// 订单标签页 - 使用 ref 以便整体替换
const orderTabs = ref<OrderTabItem[]>([
{ id: 'all', name: '全部', count: 0 },
{ id: 'pending', name: '待付款', count: 0 },
{ id: 'shipping', name: '待发货', count: 0 },
{ id: 'delivering', name: '待收货', count: 0 },
{ id: 'completed', name: '已完成', count: 0 },
{ id: 'cancelled', name: '已取消', count: 0 }
])
// Removed Mock Data
// 辅助函数:获取状态码
const getStatusByTab = (tabId: string): number => {
if (tabId == 'pending') return 1
if (tabId == 'shipping') return 2
if (tabId == 'delivering') return 3
if (tabId == 'completed') return 4
if (tabId == 'cancelled') return 5
return 0
}
// 辅助函数:解析规格文本
const parseSpecText = (specs: any): string => {
if (specs == null) return ''
if (typeof specs === 'string') return specs
// 对于对象类型尝试转为JSON字符串或简单处理
try {
return JSON.stringify(specs)
} catch (e) {
return ''
}
}
// 辅助函数:更新标签计数
const updateTabsCounts = (allOrders: any[]) => {
// 直接重新赋值整个数组
const tabsData = orderTabs.value
// 计算各状态数量
const countAll = allOrders.length
const countPending = allOrders.filter((o: any) => {
const obj = o as Record<string, any>
return obj['status'] === 1
}).length
const countShipping = allOrders.filter((o: any) => {
const obj = o as Record<string, any>
return obj['status'] === 2
}).length
const countDelivering = allOrders.filter((o: any) => {
const obj = o as Record<string, any>
return obj['status'] === 3
}).length
const countCompleted = allOrders.filter((o: any) => {
const obj = o as Record<string, any>
return obj['status'] === 4
}).length
const countCancelled = allOrders.filter((o: any) => {
const obj = o as Record<string, any>
return obj['status'] === 5
}).length
// 更新数组元素
const tabsArr = tabsData as any[]
const tab0 = tabsArr[0] as Record<string, any>
tab0['count'] = countAll
const tab1 = tabsArr[1] as Record<string, any>
tab1['count'] = countPending
const tab2 = tabsArr[2] as Record<string, any>
tab2['count'] = countShipping
const tab3 = tabsArr[3] as Record<string, any>
tab3['count'] = countDelivering
const tab4 = tabsArr[4] as Record<string, any>
tab4['count'] = countCompleted
const tab5 = tabsArr[5] as Record<string, any>
tab5['count'] = countCancelled
}
// 辅助函数:按标签筛选订单
const filterOrdersByTab = () => {
if (activeTab.value === 'all') {
orders.value = allOrdersList.value
} else {
const targetStatus = getStatusByTab(activeTab.value)
orders.value = allOrdersList.value.filter((o: OrderItem) => {
return o.status === targetStatus
})
}
}
// 加载订单数据
const loadOrders = async () => {
loading.value = true
try {
// Fetch all orders from Supabase (status=0)
const fetchedOrders = await supabaseService.getOrders(0)
// Map to View Model
const mappedOrders: any[] = []
for (let i = 0; i < fetchedOrders.length; i++) {
const order = fetchedOrders[i]
const orderObj = order as Record<string, any>
const items = orderObj['ml_order_items'] as any[]
const productsList: any[] = []
if (items != null) {
for (let j = 0; j < items.length; j++) {
const item = items[j]
const itemObj = item as Record<string, any>
const specRaw = itemObj['specifications']
const specText = specRaw != null ? parseSpecText(specRaw) : ''
productsList.push({
id: itemObj['product_id'],
name: itemObj['product_name'],
price: itemObj['price'],
image: itemObj['image_url'] ?? '/static/default-product.png',
spec: specText,
quantity: itemObj['quantity']
})
}
}
mappedOrders.push({
id: orderObj['id'],
order_no: orderObj['order_no'],
status: orderObj['order_status'],
create_time: orderObj['created_at'],
product_amount: orderObj['product_amount'] ?? 0,
shipping_fee: orderObj['shipping_fee'] ?? 0,
total_amount: orderObj['total_amount'] ?? orderObj['paid_amount'] ?? 0,
products: productsList
})
}
// Sort by created_at desc
mappedOrders.sort((a: any, b: any) => {
const aObj = a as Record<string, any>
const bObj = b as Record<string, any>
const timeA = new Date(aObj['create_time'] as string).getTime()
const timeB = new Date(bObj['create_time'] as string).getTime()
return timeB - timeA
})
// 将 mappedOrders 转换为 OrderItem[] 类型
const typedOrders: OrderItem[] = []
for (let i = 0; i < mappedOrders.length; i++) {
const mo = mappedOrders[i] as Record<string, any>
typedOrders.push({
id: mo['id'] as string,
order_no: mo['order_no'] as string,
status: mo['status'] as number,
create_time: mo['create_time'] as string,
product_amount: mo['product_amount'] as number,
shipping_fee: mo['shipping_fee'] as number,
total_amount: mo['total_amount'] as number,
products: mo['products'] as OrderProduct[]
})
}
allOrdersList.value = typedOrders
// Update tab counts
updateTabsCounts(mappedOrders)
// Apply current tab filter
filterOrdersByTab()
} catch (err) {
console.error('加载订单异常:', err)
uni.showToast({ title: '加载订单失败', icon: 'none' })
} finally {
loading.value = false
}
}
// 生命周期
onLoad((options) => {
if (options['status'] != null) {
const status = options['status'] as string
if (['all', 'pending', 'shipping', 'delivering', 'completed', 'cancelled'].includes(status)) {
activeTab.value = status
}
}
if (options['type'] != null) {
const type = options['type'] as string
if (type === 'pending') activeTab.value = 'pending'
else if (type === 'shipped') activeTab.value = 'delivering' // 映射到待收货
else if (type === 'review') activeTab.value = 'completed' // 映射到已完成
else if (type === 'refund') activeTab.value = 'all' // 申请售后默认显示全部
}
})
onShow(() => {
loadOrders()
})
const formatDate = (isoString: string): string => {
if (isoString == '') return ''
const date = new Date(isoString)
return `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
}
// 辅助函数:获取当前订单数据(必须在 performSearch 之前定义)
function getCurrentOrderData(): OrderItem[] {
return allOrdersList.value
}
// 搜索执行函数(必须在 onSearchInput 等之前定义)
const performSearch = () => {
const keyword = searchKeyword.value.trim().toLowerCase()
if (keyword == '') {
loadOrders()
return
}
// 在当前订单数据中搜索
const allOrders = getCurrentOrderData()
const filtered = allOrders.filter((order: any) => {
const orderObj = order as Record<string, any>
// 搜索订单号
const orderNo = orderObj['order_no'] as string
if (orderNo != null && orderNo.toLowerCase().includes(keyword)) {
return true
}
// 搜索商品名称
const products = orderObj['products']
if (products != null && Array.isArray(products)) {
return products.some((product: any) => {
const productObj = product as Record<string, any>
const name = productObj['name'] as string
return name != null && name.toLowerCase().includes(keyword)
})
}
return false
})
orders.value = filtered
}
// 搜索相关函数
const onSearchInput = (e: any) => {
const eObj = e as Record<string, any>
const detail = eObj['detail'] as Record<string, any>
searchKeyword.value = detail['value'] as string
performSearch()
}
const onSearchConfirm = () => {
performSearch()
}
const clearSearch = () => {
searchKeyword.value = ''
performSearch()
}
const formatSpec = (specs: any): string => {
if (specs == null) return ''
if (typeof specs === 'string') return specs
if (typeof specs === 'object') {
return JSON.stringify(specs)
}
return ''
}
// 切换标签
const switchTab = (tabId: string) => {
activeTab.value = tabId
filterOrdersByTab()
}
// 获取状态文本
const getStatusText = (status: number): string => {
if (status == 1) return '待付款'
if (status == 2) return '待发货'
if (status == 3) return '待收货'
if (status == 4) return '已完成'
if (status == 5) return '已取消'
return '未知状态'
}
// 获取状态类名
const getStatusClass = (status: number): string => {
if (status == 1) return 'status-pending'
if (status == 2) return 'status-shipping'
if (status == 3) return 'status-delivering'
if (status == 4) return 'status-completed'
if (status == 5) return 'status-cancelled'
return 'status-unknown'
}
// 下拉刷新
const onRefresh = () => {
refreshing.value = true
setTimeout(() => {
loadOrders()
refreshing.value = false
uni.showToast({
title: '刷新成功',
icon: 'success'
})
}, 1000)
}
// 上拉加载更多
const loadMore = () => {
if (loadingMore.value || !hasMore.value) return
// 暂未实现分页,直接返回
hasMore.value = false
}
// 订单操作函数
const cancelOrder = (orderId: string) => {
uni.showModal({
title: '确认取消',
content: '确定要取消此订单吗?',
success: (res) => {
if (res.confirm) {
// 这里应该是实际的API调用
uni.showToast({
title: '订单已取消',
icon: 'success'
})
// 更新订单状态
const index = orders.value.findIndex((o: any) => {
const obj = o as Record<string, any>
return obj['id'] === orderId
})
if (index !== -1) {
const orderObj = orders.value[index] as Record<string, any>
orderObj['status'] = 5
orders.value = [...orders.value]
}
}
}
})
}
const payOrder = (orderId: string) => {
uni.navigateTo({
url: `/pages/mall/consumer/payment?orderId=${orderId}`
})
}
const remindShipping = (orderId: string) => {
uni.showToast({
title: '已提醒卖家发货',
icon: 'success'
})
}
const viewLogistics = (orderId: string) => {
uni.navigateTo({
url: `/pages/mall/consumer/logistics?orderId=${orderId}`
})
}
// goReview 必须在 doConfirmReceipt 之前定义,因为 doConfirmReceipt 会调用它
const goReview = (order: any) => {
const orderObj = order as Record<string, any>
const products = orderObj['products'] as any[]
const productIds = products.map((p: any) => {
const pObj = p as Record<string, any>
const pid = pObj['id']
return pid != null ? pid as string : ''
}).join(',')
const orderId = orderObj['id'] as string
uni.navigateTo({
url: `/pages/mall/consumer/review?orderId=${orderId}&productIds=${productIds}`
})
}
const doConfirmReceipt = async (orderId: string) => {
uni.showLoading({ title: '处理中...' })
try {
const result = await supabaseService.confirmReceipt(orderId)
uni.hideLoading()
if (result.success) {
uni.showToast({
title: '收货成功',
icon: 'success'
})
// 更新本地状态
const index = orders.value.findIndex((o: any) => {
const obj = o as Record<string, any>
return obj['id'] === orderId
})
if (index !== -1) {
const orderObj = orders.value[index] as Record<string, any>
orderObj['status'] = 4
orders.value = [...orders.value]
}
// 跳转到评价页面
setTimeout(() => {
const order = orders.value.find((o: any) => {
const obj = o as Record<string, any>
return obj['id'] === orderId
})
if (order != null) {
goReview(order)
}
}, 1000)
} else {
uni.showToast({
title: result.error ?? '确认收货失败',
icon: 'none'
})
}
} catch (e) {
uni.hideLoading()
uni.showToast({
title: '系统异常',
icon: 'none'
})
}
}
const confirmReceipt = (orderId: string) => {
uni.showModal({
title: '确认收货',
content: '请确认您已收到商品,且商品无误',
success: (res) => {
if (res.confirm) {
doConfirmReceipt(orderId)
}
}
})
}
const repurchase = (order: any) => {
uni.showModal({
title: '再次购买',
content: '确定要将这些商品加入购物车吗?',
success: (res) => {
if (res.confirm) {
// 这里应该是实际的API调用
uni.showToast({
title: '已加入购物车',
icon: 'success'
})
}
}
})
}
const viewOrderDetail = (orderId: string) => {
uni.navigateTo({
url: `/pages/mall/consumer/order-detail?id=${orderId}`
})
}
const onApplyRefund = (order: any) => {
const orderObj = order as Record<string, any>
const orderId = orderObj['id']
uni.navigateTo({
url: `/pages/mall/consumer/apply-refund?orderId=${orderId}`
})
}
// 导航函数
const navigateToSearch = () => {
uni.navigateTo({ url: '/pages/mall/consumer/search' })
}
const navigateToProduct = (product: any) => {
const productObj = product as Record<string, any>
const productId = productObj['id']
uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${productId}` })
}
const goShopping = () => {
uni.switchTab({ url: '/pages/mall/consumer/index' })
}
</script>
<style>
.orders-page {
width: 100%;
flex: 1;
display: flex;
flex-direction: column;
background-color: #f5f5f5;
}
/* 头部 */
.orders-header {
background-color: white;
padding: 15px;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 1px solid #eee;
/* position: sticky; removed */
/* top: 0; removed */
z-index: 10;
}
.header-search.full-width {
display: flex;
align-items: center;
position: relative;
width: 100%;
}
.search-input {
flex: 1;
height: 36px;
border: 1px solid #ddd;
border-radius: 18px;
padding: 0 40px 0 16px;
font-size: 14px;
background-color: #f5f5f5;
color: #333;
width: 100%;
}
.search-input::placeholder {
color: #999;
font-size: 12px;
}
.search-input:focus {
border-color: #ff5000;
background-color: white;
}
.search-icon {
position: absolute;
right: 12px;
font-size: 18px;
color: #999;
}
.search-clear {
position: absolute;
right: 12px;
font-size: 20px;
color: #999;
width: 20px;
height: 20px;
line-height: 18px;
text-align: center;
border-radius: 10px; /* fixed 50% */
background-color: #ddd;
/* cursor: pointer; removed */
}
/* 标签页 */
.order-tabs {
background-color: #ffffff;
border-bottom: 1px solid #e5e5e5;
/* position: sticky; removed */
/* top: 50px; removed */
z-index: 10;
}
.tab-scroll {
width: 100%;
white-space: nowrap;
}
.tab-container {
display: flex;
flex-direction: row;
padding: 0 10px;
/* width: max-content; removed */
/* min-width: 100%; removed */
}
.tab-item {
/* 移除 flex: 1改为自适应宽度或固定最小宽度 */
padding: 15px 15px; /* 增加水平内边距 */
text-align: center;
position: relative;
display: flex;
justify-content: center;
align-items: center;
white-space: nowrap; /* 防止文字换行 */
flex-shrink: 0; /* 防止被压缩 */
}
.tab-item.active {
color: #ff5000;
font-weight: bold;
}
.tab-item.active::after {
/* content: ''; removed */
/* content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background-color: #ff5000; */
}
.active-indicator {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background-color: #ff5000;
}
.tab-name {
font-size: 14px;
}
.tab-count {
margin-left: 4px;
background-color: #ff5000;
color: white;
font-size: 10px;
padding: 1px 4px;
border-radius: 8px;
min-width: 12px;
text-align: center;
}
/* 内容区 */
.orders-content {
flex: 1;
}
/* 空状态 */
.empty-orders {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 20px;
text-align: center;
}
.empty-icon {
font-size: 80px;
color: #ddd;
margin-bottom: 20px;
}
.empty-title {
font-size: 18px;
color: #666;
margin-bottom: 10px;
}
.empty-desc {
font-size: 14px;
color: #999;
margin-bottom: 30px;
}
.go-shopping-btn {
background-color: #ff5000;
color: white;
border: none;
border-radius: 25px;
padding: 10px 40px;
font-size: 16px;
}
/* 订单列表 */
.order-list {
padding: 10px;
}
.order-card {
background-color: white;
border-radius: 10px;
margin-bottom: 10px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
/* 订单头部 */
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid #f5f5f5;
}
.order-no {
font-size: 14px;
color: #666;
}
.order-status {
font-size: 14px;
font-weight: bold;
}
.status-pending {
color: #ff5000;
}
.status-shipping {
color: #ff9500;
}
.status-delivering {
color: #007aff;
}
.status-completed {
color: #34c759;
}
.status-cancelled {
color: #999;
}
/* 订单商品 */
.order-products {
padding: 15px;
}
.order-product {
display: flex;
margin-bottom: 15px;
}
.order-product:last-child {
margin-bottom: 0;
}
.product-image {
width: 80px;
height: 80px;
border-radius: 8px;
margin-right: 15px;
}
.product-info {
flex: 1;
}
.product-name {
font-size: 15px;
color: #333;
margin-bottom: 5px;
line-height: 1.4;
}
.product-spec {
font-size: 13px;
color: #999;
margin-bottom: 10px;
}
.product-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.product-price {
font-size: 16px;
color: #ff5000;
font-weight: bold;
}
.product-quantity {
font-size: 14px;
color: #666;
}
/* 订单信息 */
.order-info {
padding: 15px;
border-top: 1px solid #f5f5f5;
border-bottom: 1px solid #f5f5f5;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-row.total {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #f5f5f5;
}
.info-label {
font-size: 14px;
color: #666;
}
.info-value {
font-size: 14px;
color: #333;
}
.total-price {
font-size: 18px;
color: #ff5000;
font-weight: bold;
}
/* 订单操作 */
.order-actions {
padding: 15px;
}
.action-buttons {
display: flex;
justify-content: flex-end;
/* gap: 10px; removed */
}
.action-btn {
padding: 6px 15px;
border-radius: 15px;
font-size: 13px;
border: 1px solid;
background-color: transparent; /* fixed background: none */
margin-left: 10px; /* alternative to gap */
}
.action-btn.cancel {
color: #666;
border-color: #ccc;
}
.action-btn.pay {
color: #ff5000;
border-color: #ff5000;
}
.action-btn.remind {
color: #666;
border-color: #ccc;
}
.action-btn.view {
color: #666;
border-color: #ccc;
}
.action-btn.confirm {
color: #34c759;
border-color: #34c759;
}
.action-btn.review {
color: #ff9500;
border-color: #ff9500;
}
.action-btn.repurchase {
color: #ff5000;
border-color: #ff5000;
}
/* 加载更多 */
.loading-more {
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-spinner {
width: 24px;
height: 24px;
border: 2px solid #f0f5ff;
border-top-color: #ff5000;
border-radius: 12px;
margin-bottom: 10px;
}
.no-more {
text-align: center;
color: #999;
font-size: 13px;
padding: 20px 0;
}
/* 安全区域 */
.safe-area {
height: 20px;
}
/* 底部导航占位 */
.tabbar-placeholder {
height: 50px;
background-color: #f5f5f5;
}
/* 响应式适配 */
@media screen and (max-width: 320px) {
.tab-item {
padding: 0 10px;
margin-right: 5px;
}
.action-btn {
padding: 6px 10px;
font-size: 12px;
}
}
@media screen and (min-width: 415px) {
.order-card {
margin-bottom: 15px;
}
}
</style>