425 lines
10 KiB
Vue
425 lines
10 KiB
Vue
<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>
|