feat: 更新订单和用户视图,增强订单信息展示,支持充值类型订单
This commit is contained in:
@@ -10,8 +10,9 @@
|
||||
<p class="text-sm text-slate-500">订单号: {{ order.id }}</p>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<span class="px-3 py-1 rounded-full text-sm font-bold bg-green-50 text-green-600 border border-green-100" v-if="order.status === 'completed'">交易成功</span>
|
||||
<span class="px-3 py-1 rounded-full text-sm font-bold bg-orange-50 text-orange-600 border border-orange-100" v-else-if="order.status === 'unpaid'">待支付</span>
|
||||
<span class="px-3 py-1 rounded-full text-sm font-bold border" :class="statusBadgeClass(order.status)">
|
||||
{{ order.status_description }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,22 +30,22 @@
|
||||
<div class="w-9 h-9 rounded-full bg-green-500 text-white flex items-center justify-center font-bold text-sm border-4 border-slate-50"><i class="pi pi-check"></i></div>
|
||||
<div class="text-center">
|
||||
<div class="text-sm font-bold text-slate-900">提交订单</div>
|
||||
<div class="text-xs text-slate-500 mt-1">{{ order.createTime }}</div>
|
||||
<div class="text-xs text-slate-500 mt-1">{{ order.create_time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Step 2 -->
|
||||
<div class="flex flex-col items-center gap-2 w-24">
|
||||
<div class="w-9 h-9 rounded-full flex items-center justify-center font-bold text-sm border-4 border-slate-50 transition-colors" :class="order.payTime ? 'bg-green-500 text-white' : 'bg-slate-200 text-slate-500'"><i class="pi pi-wallet"></i></div>
|
||||
<div class="w-9 h-9 rounded-full flex items-center justify-center font-bold text-sm border-4 border-slate-50 transition-colors" :class="order.pay_time ? 'bg-green-500 text-white' : 'bg-slate-200 text-slate-500'"><i class="pi pi-wallet"></i></div>
|
||||
<div class="text-center">
|
||||
<div class="text-sm font-bold" :class="order.payTime ? 'text-slate-900' : 'text-slate-500'">付款成功</div>
|
||||
<div class="text-xs text-slate-500 mt-1" v-if="order.payTime">{{ order.payTime }}</div>
|
||||
<div class="text-sm font-bold" :class="order.pay_time ? 'text-slate-900' : 'text-slate-500'">付款成功</div>
|
||||
<div class="text-xs text-slate-500 mt-1" v-if="order.pay_time">{{ order.pay_time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Step 3 -->
|
||||
<div class="flex flex-col items-center gap-2 w-24">
|
||||
<div class="w-9 h-9 rounded-full flex items-center justify-center font-bold text-sm border-4 border-slate-50 transition-colors" :class="order.status === 'completed' ? 'bg-green-500 text-white' : 'bg-slate-200 text-slate-500'"><i class="pi pi-box"></i></div>
|
||||
<div class="text-center">
|
||||
<div class="text-sm font-bold" :class="order.status === 'completed' ? 'text-slate-900' : 'text-slate-500'">{{ order.isVirtual ? '自动发货' : '商家发货' }}</div>
|
||||
<div class="text-sm font-bold" :class="order.status === 'completed' ? 'text-slate-900' : 'text-slate-500'">{{ order.is_virtual ? '自动发货' : '商家发货' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Step 4 -->
|
||||
@@ -61,18 +62,38 @@
|
||||
<!-- Info Stack (Single Column Layout) -->
|
||||
<div class="space-y-8">
|
||||
<!-- 1. Product Info -->
|
||||
<div class="border border-slate-200 rounded-lg overflow-hidden">
|
||||
<div class="bg-slate-50 px-6 py-3 border-b border-slate-200 font-bold text-slate-900 text-lg">商品信息</div>
|
||||
<div class="p-6 flex flex-col sm:flex-row gap-6">
|
||||
<img :src="order.cover" class="w-32 h-20 object-cover rounded bg-slate-100 flex-shrink-0">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-bold text-slate-900 text-xl mb-2">{{ order.title }}</h3>
|
||||
<div class="text-base text-slate-500 mb-3">{{ order.sku || '默认规格' }}</div>
|
||||
<div v-if="order.isVirtual" class="inline-flex items-center px-2.5 py-1 rounded text-sm font-medium bg-blue-50 text-blue-600">虚拟商品</div>
|
||||
<div class="border border-slate-200 rounded-lg overflow-hidden" v-if="order.id">
|
||||
<div class="bg-slate-50 px-6 py-3 border-b border-slate-200 font-bold text-slate-900 text-lg">
|
||||
{{ order.type === 'recharge' ? '服务信息' : '商品信息' }}
|
||||
</div>
|
||||
|
||||
<!-- Recharge View -->
|
||||
<div v-if="order.type === 'recharge'" class="p-6 flex items-center gap-6">
|
||||
<div class="w-20 h-20 rounded-full bg-slate-50 flex items-center justify-center text-primary-500">
|
||||
<i class="pi pi-wallet text-3xl"></i>
|
||||
</div>
|
||||
<div class="text-right sm:text-right text-left">
|
||||
<div class="font-bold text-slate-900 text-xl">¥ {{ order.price }}</div>
|
||||
<div class="text-slate-500 text-base mt-1">x {{ order.quantity }}</div>
|
||||
<div>
|
||||
<h3 class="font-bold text-slate-900 text-xl mb-2">账户余额充值</h3>
|
||||
<div class="text-slate-500">充值金额将会即时到账</div>
|
||||
</div>
|
||||
<div class="ml-auto text-right">
|
||||
<div class="font-bold text-slate-900 text-xl">¥ {{ order.amount }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Items View -->
|
||||
<div v-else class="divide-y divide-slate-100">
|
||||
<div v-for="(item, idx) in order.items" :key="idx" class="p-6 flex flex-col sm:flex-row gap-6">
|
||||
<img :src="item.cover" class="w-32 h-20 object-cover rounded bg-slate-100 flex-shrink-0">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-bold text-slate-900 text-xl mb-2">{{ item.title }}</h3>
|
||||
<div class="text-base text-slate-500 mb-3">{{ item.sku || '默认规格' }}</div>
|
||||
<div v-if="order.is_virtual" class="inline-flex items-center px-2.5 py-1 rounded text-sm font-medium bg-blue-50 text-blue-600">虚拟商品</div>
|
||||
</div>
|
||||
<div class="text-right sm:text-right text-left">
|
||||
<div class="font-bold text-slate-900 text-xl">¥ {{ item.price }}</div>
|
||||
<!-- Quantity is on order level usually for single-SKU, or item level? Assuming item price is unit price * quantity or just price paid -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -81,16 +102,12 @@
|
||||
<div class="flex flex-col gap-2 ml-auto max-w-sm">
|
||||
<div class="flex justify-between text-base text-slate-600">
|
||||
<span>商品总额</span>
|
||||
<span>¥ {{ (order.price * order.quantity).toFixed(2) }}</span>
|
||||
<span>¥ {{ order.amount }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-base text-slate-600">
|
||||
<span>运费</span>
|
||||
<span>¥ 0.00</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-base text-slate-600">
|
||||
<span>优惠券</span>
|
||||
<span class="text-red-600">- ¥ 0.00</span>
|
||||
</div>
|
||||
<div class="border-t border-slate-200 my-2 pt-4 flex justify-between items-center">
|
||||
<span class="font-bold text-slate-900 text-lg">实付金额</span>
|
||||
<span class="text-3xl font-bold text-red-600">¥ {{ order.amount }}</span>
|
||||
@@ -108,12 +125,12 @@
|
||||
<h3 class="font-bold text-slate-900 text-lg mb-4 border-l-4 border-primary-500 pl-3">订单信息</h3>
|
||||
<div class="space-y-3 text-slate-600">
|
||||
<p>订单编号: <span class="text-slate-900 select-all">{{ order.id }}</span> <button class="text-primary-600 ml-2 hover:underline font-medium cursor-pointer">复制</button></p>
|
||||
<p>创建时间: {{ order.createTime }}</p>
|
||||
<p v-if="order.payTime">付款时间: {{ order.payTime }}</p>
|
||||
<p>创建时间: {{ order.create_time }}</p>
|
||||
<p v-if="order.pay_time">付款时间: {{ order.pay_time }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!order.isVirtual">
|
||||
<div v-if="!order.is_virtual && order.type !== 'recharge'">
|
||||
<h3 class="font-bold text-slate-900 text-lg mb-4 border-l-4 border-primary-500 pl-3">收货信息</h3>
|
||||
<div class="space-y-2 text-slate-600">
|
||||
<p class="font-bold text-slate-900 text-lg">张三 138****8888</p>
|
||||
@@ -123,13 +140,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Merchant & Actions -->
|
||||
<div class="space-y-6">
|
||||
<div class="space-y-6" v-if="order.tenant_id !== '0' && order.type !== 'recharge'">
|
||||
<div>
|
||||
<h3 class="font-bold text-slate-900 text-lg mb-4 border-l-4 border-primary-500 pl-3">商家信息</h3>
|
||||
<div class="flex items-center gap-4 p-4 bg-slate-50 rounded-lg">
|
||||
<img :src="order.tenantAvatar" class="w-12 h-12 rounded-full">
|
||||
<img :src="order.tenant_avatar || 'https://api.dicebear.com/7.x/initials/svg?seed=' + order.tenant_name" class="w-12 h-12 rounded-full">
|
||||
<div>
|
||||
<div class="font-bold text-slate-900">{{ order.tenantName }}</div>
|
||||
<div class="font-bold text-slate-900">{{ order.tenant_name }}</div>
|
||||
<button class="text-primary-600 text-sm mt-1 hover:underline cursor-pointer">联系商家</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,32 +159,41 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { userApi } from '../../api/user';
|
||||
|
||||
const route = useRoute();
|
||||
const orderId = route.params.id;
|
||||
const order = ref({});
|
||||
const loading = ref(true);
|
||||
|
||||
// Mock Data logic based on ID logic or just static for demo
|
||||
const order = ref({
|
||||
id: orderId || '82934712',
|
||||
createTime: '2025-12-24 14:30:00',
|
||||
payTime: '2025-12-24 14:30:05',
|
||||
status: 'completed',
|
||||
isVirtual: true,
|
||||
cover: 'https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=100&q=60',
|
||||
title: '《霸王别姬》全本实录珍藏版',
|
||||
sku: '高清数字版',
|
||||
price: 9.90,
|
||||
quantity: 1,
|
||||
amount: '9.90',
|
||||
tenantName: '梅派传人小林',
|
||||
tenantAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Master1'
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await userApi.getOrder(orderId);
|
||||
order.value = res || {};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
const progressWidth = computed(() => {
|
||||
if (order.value.status === 'completed') return '100%';
|
||||
if (order.value.status === 'paid') return '66%';
|
||||
if (order.value.status === 'completed' || order.value.status === 'paid') return '100%';
|
||||
if (order.value.status === 'paid') return '66%'; // 'paid' can be 100% for virtual/recharge
|
||||
return '33%';
|
||||
});
|
||||
|
||||
const statusBadgeClass = (status) => {
|
||||
const map = {
|
||||
created: 'bg-orange-50 text-orange-600 border-orange-100',
|
||||
paid: 'bg-blue-50 text-blue-600 border-blue-100',
|
||||
completed: 'bg-green-50 text-green-600 border-green-100',
|
||||
refunding: 'bg-purple-50 text-purple-600 border-purple-100',
|
||||
refunded: 'bg-slate-100 text-slate-500 border-slate-200',
|
||||
cancelled: 'bg-slate-100 text-slate-400 border-slate-200'
|
||||
};
|
||||
return map[status] || 'bg-slate-50 text-slate-500 border-slate-100';
|
||||
};
|
||||
</script>
|
||||
@@ -50,19 +50,24 @@
|
||||
<div class="space-y-4">
|
||||
<div v-for="order in recentOrders" :key="order.id" @click="$router.push(`/me/orders/${order.id}`)"
|
||||
class="flex items-center gap-4 p-4 border border-slate-100 rounded-lg hover:border-primary-100 hover:shadow-sm transition-all cursor-pointer active:scale-[0.99] group">
|
||||
<div class="w-16 h-16 bg-slate-100 rounded object-cover flex-shrink-0">
|
||||
<img v-if="order.items && order.items.length > 0"
|
||||
<div class="w-16 h-16 bg-slate-100 rounded flex-shrink-0 flex items-center justify-center relative overflow-hidden">
|
||||
<template v-if="order.type === 'recharge' || !order.items?.length">
|
||||
<i class="pi pi-wallet text-2xl text-primary-500"></i>
|
||||
</template>
|
||||
<img v-else-if="order.items && order.items.length > 0"
|
||||
:src="order.items[0].cover"
|
||||
class="w-full h-full object-cover rounded transition-transform group-hover:scale-105">
|
||||
<i v-else class="pi pi-box text-2xl text-slate-400"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-bold text-slate-900 truncate group-hover:text-primary-600 transition-colors">
|
||||
{{ order.items && order.items.length > 0 ? order.items[0].title : '未知商品' }}</h3>
|
||||
{{ (order.type === 'recharge' || !order.items?.length) ? '账户充值' : (order.items?.[0]?.title || '未知商品') }}
|
||||
</h3>
|
||||
<div class="text-sm text-slate-500 mt-1">{{ order.create_time }} · 订单号: {{ order.id }}</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="font-bold text-slate-900">¥ {{ order.amount }}</div>
|
||||
<div class="text-sm text-green-600 mt-1">{{ order.status }}</div>
|
||||
<div class="text-sm mt-1 font-medium" :class="statusColor(order.status)">{{ order.status_description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="recentOrders.length === 0" class="text-center text-slate-400 py-4">暂无订单</div>
|
||||
@@ -90,22 +95,38 @@ const wallet = ref({ balance: 0, points: 0 });
|
||||
const couponCount = ref(0);
|
||||
const recentOrders = ref([]);
|
||||
|
||||
const statusColor = (status) => {
|
||||
const map = {
|
||||
created: 'text-orange-600',
|
||||
paid: 'text-blue-600',
|
||||
completed: 'text-green-600',
|
||||
refunding: 'text-purple-600',
|
||||
refunded: 'text-slate-500',
|
||||
cancelled: 'text-slate-400'
|
||||
};
|
||||
return map[status] || 'text-slate-500';
|
||||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const w = await userApi.getWallet();
|
||||
wallet.value.balance = w.balance || 0;
|
||||
|
||||
const u = await userApi.getMe();
|
||||
wallet.value.points = u.points || 0;
|
||||
|
||||
const c = await userApi.getCoupons('unused');
|
||||
couponCount.value = c.length;
|
||||
|
||||
const o = await userApi.getOrders('all');
|
||||
recentOrders.value = (o || []).slice(0, 3);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
// Fetch Wallet
|
||||
userApi.getWallet()
|
||||
.then(w => { wallet.value.balance = w.balance || 0; })
|
||||
.catch(e => console.error("Wallet fetch failed:", e));
|
||||
|
||||
// Fetch User Info (Points)
|
||||
userApi.getMe()
|
||||
.then(u => { wallet.value.points = u.points || 0; })
|
||||
.catch(e => console.error("Me fetch failed:", e));
|
||||
|
||||
// Fetch Coupons
|
||||
userApi.getCoupons('unused')
|
||||
.then(c => { couponCount.value = (c || []).length; })
|
||||
.catch(e => console.error("Coupons fetch failed:", e));
|
||||
|
||||
// Fetch Recent Orders
|
||||
userApi.getOrders('all')
|
||||
.then(o => { recentOrders.value = (o || []).slice(0, 3); })
|
||||
.catch(e => console.error("Orders fetch failed:", e));
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -32,25 +32,40 @@
|
||||
<!-- Order Header -->
|
||||
<div class="bg-slate-50 px-4 py-3 flex items-center justify-between text-sm text-slate-500">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-medium text-slate-900">{{ order.date }}</span>
|
||||
<span class="font-medium text-slate-900">{{ order.create_time }}</span>
|
||||
<span class="select-all">订单号: {{ order.id }}</span>
|
||||
<span class="hover:text-primary-600 cursor-pointer transition-colors">{{ order.tenantName }} <i class="pi pi-angle-right text-xs"></i></span>
|
||||
<span class="hover:text-primary-600 cursor-pointer transition-colors" v-if="order.tenant_id !== '0'">{{ order.tenant_name }} <i class="pi pi-angle-right text-xs"></i></span>
|
||||
<span v-else class="text-slate-500">平台自营</span>
|
||||
</div>
|
||||
<div class="font-bold" :class="statusColor(order.status)">{{ statusText(order.status) }}</div>
|
||||
<div class="font-bold" :class="statusColor(order.status)">{{ order.status_description }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Order Body -->
|
||||
<div class="p-4 flex flex-col sm:flex-row gap-6">
|
||||
<!-- Product Info (Clickable Area) -->
|
||||
<div class="flex-1 flex gap-4 cursor-pointer" @click="$router.push(`/me/orders/${order.id}`)">
|
||||
<div class="w-24 h-16 bg-slate-100 rounded object-cover flex-shrink-0 relative overflow-hidden group-hover:opacity-90 transition-opacity">
|
||||
<img :src="order.cover" class="w-full h-full object-cover">
|
||||
<div v-if="order.type === 'video'" class="absolute inset-0 flex items-center justify-center bg-black/20 text-white"><i class="pi pi-play-circle"></i></div>
|
||||
<div class="w-24 h-16 bg-slate-100 rounded flex-shrink-0 relative overflow-hidden group-hover:opacity-90 transition-opacity flex items-center justify-center">
|
||||
<template v-if="order.type === 'recharge' || !order.items?.length">
|
||||
<i class="pi pi-wallet text-3xl text-primary-500"></i>
|
||||
</template>
|
||||
<template v-else-if="order.items && order.items.length > 0">
|
||||
<img :src="order.items[0].cover" class="w-full h-full object-cover">
|
||||
<!-- Assuming genre is on item, not order.type -->
|
||||
</template>
|
||||
<template v-else>
|
||||
<i class="pi pi-box text-2xl text-slate-400"></i>
|
||||
</template>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-bold text-slate-900 line-clamp-1 mb-1 group-hover:text-primary-600 transition-colors">{{ order.title }}</h3>
|
||||
<div class="text-xs text-slate-500 mb-2">{{ order.typeLabel }}</div>
|
||||
<div v-if="order.isVirtual" class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-50 text-blue-600">虚拟发货</div>
|
||||
<h3 class="font-bold text-slate-900 line-clamp-1 mb-1 group-hover:text-primary-600 transition-colors">
|
||||
{{ (order.type === 'recharge' || !order.items?.length) ? '账户充值' : (order.items?.[0]?.title || '未知商品') }}
|
||||
</h3>
|
||||
<div class="text-xs text-slate-500 mb-2">
|
||||
{{ (order.type === 'recharge' || !order.items?.length) ? '在线充值' : (order.items?.[0]?.genre || '数字内容') }}
|
||||
</div>
|
||||
<div v-if="order.is_virtual || order.type === 'recharge' || !order.items?.length" class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-50 text-blue-600">
|
||||
{{ (order.type === 'recharge' || !order.items?.length) ? '即时到账' : '虚拟发货' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,10 +76,10 @@
|
||||
<div class="text-xs text-slate-400">在线支付</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<button v-if="order.status === 'unpaid'" class="px-4 py-1.5 bg-primary-600 text-white text-sm font-medium rounded-lg hover:bg-primary-700 transition-colors shadow-sm active:scale-95 cursor-pointer">去支付</button>
|
||||
<button v-if="order.status === 'created'" class="px-4 py-1.5 bg-primary-600 text-white text-sm font-medium rounded-lg hover:bg-primary-700 transition-colors shadow-sm active:scale-95 cursor-pointer">去支付</button>
|
||||
<router-link :to="`/me/orders/${order.id}`" v-if="order.status === 'paid' || order.status === 'completed'" class="px-4 py-1.5 border border-slate-300 text-slate-700 text-sm font-medium rounded-lg hover:bg-slate-50 hover:border-slate-400 transition-colors inline-block text-center cursor-pointer active:scale-95">查看详情</router-link>
|
||||
<button v-if="order.status === 'completed'" class="px-4 py-1.5 text-primary-600 text-sm hover:underline cursor-pointer">申请售后</button>
|
||||
<button v-if="order.status === 'unpaid'" class="text-xs text-slate-400 hover:text-slate-600 cursor-pointer">取消订单</button>
|
||||
<button v-if="order.status === 'completed' && order.type !== 'recharge'" class="px-4 py-1.5 text-primary-600 text-sm hover:underline cursor-pointer">申请售后</button>
|
||||
<button v-if="order.status === 'created'" class="text-xs text-slate-400 hover:text-slate-600 cursor-pointer">取消订单</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -124,18 +139,6 @@ watch(currentTab, () => {
|
||||
// Use orders directly (filtered by backend)
|
||||
const filteredOrders = computed(() => orders.value);
|
||||
|
||||
const statusText = (status) => {
|
||||
const map = {
|
||||
created: '待支付',
|
||||
paid: '已支付',
|
||||
completed: '交易成功',
|
||||
refunding: '退款中',
|
||||
refunded: '已退款',
|
||||
cancelled: '已取消'
|
||||
};
|
||||
return map[status] || status;
|
||||
};
|
||||
|
||||
const statusColor = (status) => {
|
||||
const map = {
|
||||
created: 'text-orange-600',
|
||||
|
||||
Reference in New Issue
Block a user