JewelryMall/miniprogram/pages/order/list.vue
2026-03-05 00:43:04 +08:00

425 lines
10 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="order-list">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="custom-navbar__content" :style="{ height: navBarHeight + 'px' }">
<image class="custom-navbar__back" src="/static/ic_back.png" mode="aspectFit" @click="goBack" />
<text class="custom-navbar__title">我的订单</text>
<view class="custom-navbar__placeholder" />
</view>
</view>
<view :style="{ height: (statusBarHeight + navBarHeight) + 'px' }" />
<!-- Tab 固定 -->
<view class="tab-bar" :style="{ position: 'fixed', top: (statusBarHeight + navBarHeight) + 'px', left: 0, right: 0, zIndex: 99 }">
<view v-for="tab in tabs" :key="tab.value" class="tab-item" :class="{ 'tab-item--active': activeTab === tab.value }" @click="activeTab = tab.value">
<text>{{ tab.label }}</text>
<view v-if="activeTab === tab.value" class="tab-item__line" />
</view>
</view>
<view style="height: 88rpx;" />
<!-- 订单列表 -->
<view v-if="filteredOrders.length === 0 && !loading" class="empty-tip">
<image src="/static/ic_empty.png" class="empty-tip__img" mode="aspectFit" />
<text>暂无订单</text>
</view>
<view v-for="order in filteredOrders" :key="order.id" class="order-card">
<!-- 订单头部 -->
<view class="order-card__header">
<text class="order-card__no">订单编号:{{ order.orderNo }}</text>
<view class="order-card__status" :class="'status--' + order.status">
<text>{{ statusLabel(order.status) }}</text>
</view>
</view>
<!-- 商品列表 -->
<view class="order-card__items">
<view v-for="item in (order.items || [])" :key="item.id" class="order-item">
<image class="order-item__img" :src="fullUrl(item.product?.thumb || item.product?.bannerImages?.[0] || '')" mode="aspectFill" />
<view class="order-item__info">
<text class="order-item__name">{{ item.product?.name }}</text>
<view class="order-item__specs">
<text>款号:{{ item.specData?.modelName }}</text>
<text>商品型号:{{ item.specData?.fineness }}</text>
<text>成色:{{ item.specData?.mainStone }}</text>
<text>主石:{{ item.specData?.mainStone }}</text>
<text v-if="item.specData?.subStone">副石:{{ item.specData?.subStone }}</text>
<text>手寸:{{ item.specData?.ringSize }}</text>
<text>金料总重:{{ item.specData?.goldTotalWeight }}</text>
</view>
<text class="order-item__price">¥{{ item.unitPrice }}元</text>
</view>
</view>
</view>
<!-- 物流信息(待收货/已收货) -->
<view v-if="(order.status === 'shipped' || order.status === 'received') && order.shippingNo" class="order-card__shipping">
<text class="shipping-label">物流:{{ order.shippingCompany }}</text>
<view class="shipping-no-row">
<text class="shipping-no">单号:{{ order.shippingNo }}</text>
<text class="shipping-copy" @click.stop="copyShippingNo(order.shippingNo)">复制</text>
</view>
</view>
<!-- 订单底部 -->
<view class="order-card__footer">
<view class="order-card__footer-left">
<text class="order-card__total">合计:<text class="order-card__total-price">¥{{ order.totalPrice }}</text></text>
<text class="order-card__time">{{ formatTime(order.createdAt) }}</text>
</view>
<view class="order-card__detail-btn" @click="goDetail(order.id)">
<text>订单详情</text>
</view>
</view>
</view>
<view v-if="loading" class="loading-tip">
<text>加载中...</text>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
// @ts-ignore
import { onShow } from '@dcloudio/uni-app'
import type { Order, OrderStatus } from '../../types'
import { getOrderList } from '../../api/order'
import { useOrderStore } from '../../store/order'
import { BASE_URL } from '../../utils/request'
const orderStore = useOrderStore()
const orders = ref<Order[]>([])
const loading = ref(false)
const activeTab = ref('all')
const tabs = [
{ label: '全部订单', value: 'all' },
{ label: '未付款', value: 'pending' },
{ label: '待发货', value: 'paid' },
{ label: '待收货', value: 'shipped' },
{ label: '已收货', value: 'received' },
]
const filteredOrders = computed(() => {
if (activeTab.value === 'all') return orders.value
return orders.value.filter(o => o.status === activeTab.value)
})
const statusBarHeight = ref(20)
const navBarHeight = ref(44)
try {
const sysInfo = uni.getSystemInfoSync()
statusBarHeight.value = sysInfo.statusBarHeight || 20
// #ifdef MP-WEIXIN
const menuBtn = uni.getMenuButtonBoundingClientRect()
navBarHeight.value = (menuBtn.top - (sysInfo.statusBarHeight || 20)) * 2 + menuBtn.height
// #endif
} catch { /* fallback */ }
const STATUS_MAP: Record<string, string> = {
pending: '待付款',
paid: '待发货',
shipped: '待收货',
received: '已收货',
cancelled: '已取消',
}
function statusLabel(status: OrderStatus) {
return STATUS_MAP[status] || status
}
function fullUrl(path: string): string {
if (!path) return ''
if (path.startsWith('http')) return path
return BASE_URL + path
}
function formatTime(iso: string): string {
const d = new Date(iso)
const Y = d.getFullYear()
const M = String(d.getMonth() + 1).padStart(2, '0')
const D = String(d.getDate()).padStart(2, '0')
const h = String(d.getHours()).padStart(2, '0')
const m = String(d.getMinutes()).padStart(2, '0')
const s = String(d.getSeconds()).padStart(2, '0')
return `${Y}-${M}-${D}${h}:${m}:${s}`
}
function goBack() {
uni.navigateBack({ delta: 1 })
}
function goDetail(id: number) {
uni.navigateTo({ url: `/pages/order/detail?id=${id}` })
}
function copyShippingNo(no: string) {
uni.setClipboardData({
data: no,
success() {
uni.showToast({ title: '物流单号已复制', icon: 'success' })
}
})
}
async function loadOrders() {
loading.value = true
try {
const data = await getOrderList()
orders.value = data
orderStore.setOrders(data)
} catch {
uni.showToast({ title: '加载订单失败', icon: 'none' })
} finally {
loading.value = false
}
}
onMounted(() => loadOrders())
onShow(() => loadOrders())
</script>
<style scoped>
.order-list {
min-height: 100vh;
background: #f5f5f5;
}
/* 自定义导航栏 */
.custom-navbar {
background: linear-gradient(to right, #FFCFDE, #FFA6C4);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
}
.custom-navbar__content {
display: flex;
align-items: center;
padding: 0 24rpx;
}
.custom-navbar__back {
width: 44rpx;
height: 44rpx;
}
.custom-navbar__title {
flex: 1;
text-align: center;
font-size: 34rpx;
font-weight: bold;
color: #333;
}
.custom-navbar__placeholder {
width: 44rpx;
}
/* Tab 栏 */
.tab-bar {
display: flex;
background: #fff;
padding: 0 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.tab-item {
flex: 1;
text-align: center;
padding: 24rpx 0;
font-size: 28rpx;
color: #666;
position: relative;
}
.tab-item--active {
color: #333;
font-weight: 600;
}
.tab-item__line {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 48rpx;
height: 4rpx;
background: #333;
border-radius: 2rpx;
}
/* 订单卡片 */
.order-card {
background: #fff;
margin: 20rpx 24rpx;
border-radius: 16rpx;
overflow: hidden;
}
.order-card__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 24rpx 16rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.order-card__no {
font-size: 24rpx;
color: #666;
}
.order-card__status {
padding: 6rpx 20rpx;
border-radius: 20rpx;
font-size: 22rpx;
}
.status--pending {
background: #fff0f0;
color: #FF6D9B;
}
.status--paid {
background: #e8f5e9;
color: #4caf50;
}
.status--shipped {
background: #fff8e1;
color: #ff9800;
}
.status--cancelled {
background: #f5f5f5;
color: #999;
}
.status--received {
background: #e0f2f1;
color: #009688;
}
/* 商品项 */
.order-card__items {
padding: 0 24rpx;
}
.order-item {
display: flex;
padding: 20rpx 0;
border-bottom: 1rpx solid #f8f8f8;
}
.order-item:last-child {
border-bottom: none;
}
.order-item__img {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
flex-shrink: 0;
background: #f5f5f5;
margin-right: 20rpx;
}
.order-item__info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.order-item__name {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 8rpx;
}
.order-item__specs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rpx 16rpx;
font-size: 22rpx;
color: #999;
line-height: 1.7;
margin-bottom: 8rpx;
}
.order-item__price {
font-size: 30rpx;
color: #FF6D9B;
font-weight: 700;
}
/* 物流信息 */
.order-card__shipping {
padding: 16rpx 24rpx;
background: #fafafa;
font-size: 24rpx;
color: #666;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.shipping-label {
color: #333;
}
.shipping-no-row {
display: flex;
align-items: center;
gap: 16rpx;
}
.shipping-no {
color: #666;
}
.shipping-copy {
color: #FF6D9B;
font-size: 22rpx;
padding: 4rpx 16rpx;
border: 1rpx solid #FF6D9B;
border-radius: 16rpx;
}
/* 订单底部 */
.order-card__footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 24rpx;
border-top: 1rpx solid #f5f5f5;
}
.order-card__footer-left {
display: flex;
flex-direction: column;
gap: 6rpx;
}
.order-card__total {
font-size: 26rpx;
color: #333;
}
.order-card__total-price {
color: #FF6D9B;
font-weight: 700;
font-size: 30rpx;
}
.order-card__time {
font-size: 22rpx;
color: #999;
}
.order-card__detail-btn {
padding: 14rpx 36rpx;
border: 2rpx solid #FF6D9B;
border-radius: 32rpx;
font-size: 24rpx;
color: #FF6D9B;
}
/* 空状态 */
.empty-tip {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 200rpx;
color: #999;
font-size: 28rpx;
}
.empty-tip__img {
width: 300rpx;
height: 300rpx;
margin-bottom: 20rpx;
}
.loading-tip {
text-align: center;
padding: 60rpx 0;
color: #999;
font-size: 28rpx;
}
</style>