v1
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { withAdminAuth } from '@/components/admin/AdminAuth'
|
||||
import { Eye, Package, Calendar, User } from 'lucide-react'
|
||||
|
||||
interface Order {
|
||||
id: string
|
||||
orderNumber: string
|
||||
totalAmount: number
|
||||
status: string
|
||||
createdAt: string
|
||||
user: {
|
||||
id: string
|
||||
email: string
|
||||
name?: string
|
||||
username: string
|
||||
}
|
||||
orderItems: {
|
||||
id: string
|
||||
quantity: number
|
||||
price: number
|
||||
component: {
|
||||
id: string
|
||||
name: string
|
||||
brand: string
|
||||
model: string
|
||||
}
|
||||
}[]
|
||||
}
|
||||
|
||||
const statusLabels: { [key: string]: string } = {
|
||||
PENDING: '待处理',
|
||||
CONFIRMED: '已确认',
|
||||
PROCESSING: '处理中',
|
||||
SHIPPED: '已发货',
|
||||
DELIVERED: '已送达',
|
||||
CANCELLED: '已取消'
|
||||
}
|
||||
|
||||
const statusColors: { [key: string]: string } = {
|
||||
PENDING: 'bg-yellow-100 text-yellow-800',
|
||||
CONFIRMED: 'bg-blue-100 text-blue-800',
|
||||
PROCESSING: 'bg-purple-100 text-purple-800',
|
||||
SHIPPED: 'bg-green-100 text-green-800',
|
||||
DELIVERED: 'bg-green-100 text-green-800',
|
||||
CANCELLED: 'bg-red-100 text-red-800'
|
||||
}
|
||||
|
||||
function AdminOrdersPage() {
|
||||
const [orders, setOrders] = useState<Order[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [selectedOrder, setSelectedOrder] = useState<Order | null>(null)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
loadOrders()
|
||||
}, [])
|
||||
|
||||
const loadOrders = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/admin/orders')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setOrders(data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载订单失败:', error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const updateOrderStatus = async (orderId: string, newStatus: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/orders/${orderId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ status: newStatus }),
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
await loadOrders()
|
||||
if (selectedOrder && selectedOrder.id === orderId) {
|
||||
setSelectedOrder({ ...selectedOrder, status: newStatus })
|
||||
}
|
||||
} else {
|
||||
alert('更新订单状态失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新订单状态失败:', error)
|
||||
alert('更新订单状态失败')
|
||||
}
|
||||
}
|
||||
|
||||
const viewOrderDetails = (order: Order) => {
|
||||
setSelectedOrder(order)
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center py-12">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-3xl font-bold text-gray-900">订单管理</h1>
|
||||
<div className="text-sm text-gray-600">
|
||||
共 {orders.length} 个订单
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Orders Table */}
|
||||
<div className="bg-white shadow-md rounded-lg overflow-hidden">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
订单号
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
用户
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
金额
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
状态
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
创建时间
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
操作
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{orders.map((order) => (
|
||||
<tr key={order.id}>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{order.orderNumber}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{order.orderItems.length} 个配件
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<User className="h-4 w-4 text-gray-400 mr-2" /> <div>
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{order.user?.name || order.user?.username || '未知用户'}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{order.user?.email || '无邮箱'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
¥{order.totalAmount.toLocaleString()}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<select
|
||||
value={order.status}
|
||||
onChange={(e) => updateOrderStatus(order.id, e.target.value)}
|
||||
className={`inline-flex px-2 py-1 text-xs rounded-full border-0 ${statusColors[order.status]}`}
|
||||
>
|
||||
{Object.entries(statusLabels).map(([status, label]) => (
|
||||
<option key={status} value={status}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<div className="flex items-center">
|
||||
<Calendar className="h-4 w-4 mr-2" />
|
||||
{new Date(order.createdAt).toLocaleDateString('zh-CN')}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<button
|
||||
onClick={() => viewOrderDetails(order)}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Order Details Modal */}
|
||||
{showModal && selectedOrder && (
|
||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<h2 className="text-xl font-bold mb-4">订单详情</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">订单号</label>
|
||||
<div className="text-sm text-gray-900">{selectedOrder.orderNumber}</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">状态</label>
|
||||
<span className={`inline-flex px-2 py-1 text-xs rounded-full ${statusColors[selectedOrder.status]}`}>
|
||||
{statusLabels[selectedOrder.status]}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">用户</label> <div className="text-sm text-gray-900">
|
||||
{selectedOrder.user?.name || selectedOrder.user?.username || '未知用户'}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">{selectedOrder.user?.email || '无邮箱'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">总金额</label>
|
||||
<div className="text-sm text-gray-900">¥{selectedOrder.totalAmount.toLocaleString()}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">订单商品</label>
|
||||
<div className="border rounded-lg">
|
||||
{selectedOrder.orderItems.map((item) => (
|
||||
<div key={item.id} className="p-4 border-b last:border-b-0">
|
||||
<div className="flex justify-between items-center"> <div>
|
||||
<div className="font-medium">{item.component?.name || '未知商品'}</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{item.component?.brand || '未知品牌'} {item.component?.model || ''}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="font-medium">¥{item.price.toLocaleString()}</div>
|
||||
<div className="text-sm text-gray-500">数量: {item.quantity}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">创建时间</label>
|
||||
<div className="text-sm text-gray-900">
|
||||
{new Date(selectedOrder.createdAt).toLocaleString('zh-CN')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-4 pt-6">
|
||||
<button
|
||||
onClick={() => setShowModal(false)}
|
||||
className="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAdminAuth(AdminOrdersPage)
|
||||
Reference in New Issue
Block a user