181 lines
5.7 KiB
Vue
181 lines
5.7 KiB
Vue
<template>
|
|
<div class="aksrt-page-container">
|
|
<PageHeader title="商城&商品" desc="商品管理、订单系统、库存管理等电商功能。" />
|
|
|
|
<div class="aksrt-shop-dashboard">
|
|
<!-- 统计卡片 -->
|
|
<div class="aksrt-shop-dashboard__stats">
|
|
<div class="aksrt-shop-stat-card">
|
|
<div class="aksrt-shop-stat-card__icon"><i class="fas fa-box"></i></div>
|
|
<div class="aksrt-shop-stat-card__info">
|
|
<span class="aksrt-shop-stat-card__value">{{ overview.product_count }}</span>
|
|
<span class="aksrt-shop-stat-card__label">商品数量</span>
|
|
</div>
|
|
</div>
|
|
<div class="aksrt-shop-stat-card">
|
|
<div class="aksrt-shop-stat-card__icon"><i class="fas fa-file-invoice"></i></div>
|
|
<div class="aksrt-shop-stat-card__info">
|
|
<span class="aksrt-shop-stat-card__value">{{ overview.order_count }}</span>
|
|
<span class="aksrt-shop-stat-card__label">订单数量</span>
|
|
</div>
|
|
</div>
|
|
<div class="aksrt-shop-stat-card">
|
|
<div class="aksrt-shop-stat-card__icon"><i class="fas fa-coins"></i></div>
|
|
<div class="aksrt-shop-stat-card__info">
|
|
<span class="aksrt-shop-stat-card__value">{{ revenueDisplay }}</span>
|
|
<span class="aksrt-shop-stat-card__label">累计收入 (元)</span>
|
|
</div>
|
|
</div>
|
|
<div class="aksrt-shop-stat-card">
|
|
<div class="aksrt-shop-stat-card__icon"><i class="fas fa-clock"></i></div>
|
|
<div class="aksrt-shop-stat-card__info">
|
|
<span class="aksrt-shop-stat-card__value">{{ overview.order_pending }}</span>
|
|
<span class="aksrt-shop-stat-card__label">待处理订单</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 最近商品 -->
|
|
<n-card class="aksrt-section-card" title="最近商品">
|
|
<n-data-table
|
|
v-if="products.length"
|
|
:columns="productColumns"
|
|
:data="products"
|
|
:bordered="false"
|
|
size="small"
|
|
/>
|
|
<n-empty v-else description="暂无商品" />
|
|
</n-card>
|
|
|
|
<!-- 最近订单 -->
|
|
<n-card class="aksrt-section-card" title="最近订单" style="margin-top:16px;">
|
|
<n-data-table
|
|
v-if="orders.length"
|
|
:columns="orderColumns"
|
|
:data="orders"
|
|
:bordered="false"
|
|
size="small"
|
|
/>
|
|
<n-empty v-else description="暂无订单" />
|
|
</n-card>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted, h } from 'vue'
|
|
import { NTag } from 'naive-ui'
|
|
import PageHeader from '@/components/PageHeader.vue'
|
|
import api from '@/api/index.js'
|
|
|
|
const overview = ref({ product_count: 0, order_count: 0, order_pending: 0, revenue_total: 0 })
|
|
const products = ref([])
|
|
const orders = ref([])
|
|
|
|
const revenueDisplay = computed(() => (overview.value.revenue_total || 0).toFixed(2))
|
|
|
|
const statusMap = {
|
|
created: { label: '已创建', type: 'default' },
|
|
pending: { label: '待支付', type: 'warning' },
|
|
paid: { label: '已支付', type: 'success' },
|
|
delivered: { label: '已发货', type: 'info' },
|
|
completed: { label: '已完成', type: 'success' },
|
|
cancelled: { label: '已取消', type: 'default' },
|
|
expired: { label: '已过期', type: 'default' },
|
|
refunding: { label: '退款中', type: 'warning' },
|
|
refunded: { label: '已退款', type: 'error' },
|
|
}
|
|
|
|
const productColumns = [
|
|
{ title: '商品名称', key: 'title', ellipsis: { tooltip: true } },
|
|
{ title: '价格', key: 'price', width: 100, render: (row) => '¥' + (row.price || 0).toFixed(2) },
|
|
{ title: '库存', key: 'stock', width: 80 },
|
|
{ title: '销量', key: 'sales', width: 80 },
|
|
{ title: '创建时间', key: 'created', width: 160 },
|
|
]
|
|
|
|
const orderColumns = [
|
|
{ title: '订单号', key: 'order_number', width: 180, ellipsis: { tooltip: true } },
|
|
{ title: '产品类型', key: 'product_type', width: 100 },
|
|
{ title: '金额', key: 'amount', width: 100, render: (row) => '¥' + (row.amount || 0).toFixed(2) },
|
|
{
|
|
title: '状态',
|
|
key: 'status',
|
|
width: 100,
|
|
render: (row) => {
|
|
const s = statusMap[row.status] || { label: row.status, type: 'default' }
|
|
return h(NTag, { size: 'small', type: s.type }, () => s.label)
|
|
},
|
|
},
|
|
{ title: '创建时间', key: 'created_at', width: 160 },
|
|
]
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const [overviewRes, productsRes, ordersRes] = await Promise.all([
|
|
api.request('/shop/overview'),
|
|
api.request('/shop/products?per_page=5'),
|
|
api.request('/shop/orders?per_page=5'),
|
|
])
|
|
if (overviewRes?.success && overviewRes.data) {
|
|
overview.value = { ...overview.value, ...overviewRes.data }
|
|
}
|
|
if (productsRes?.success && productsRes.data) {
|
|
products.value = productsRes.data.items || []
|
|
}
|
|
if (ordersRes?.success && ordersRes.data) {
|
|
orders.value = ordersRes.data.items || []
|
|
}
|
|
} catch {
|
|
// endpoints may not be available yet
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.aksrt-shop-dashboard__stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 16px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.aksrt-shop-stat-card {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
padding: 20px;
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
border: 1px solid #e8e8e8;
|
|
}
|
|
|
|
.aksrt-shop-stat-card__icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 8px;
|
|
background: #f0f5ff;
|
|
color: #1890ff;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.aksrt-shop-stat-card__info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.aksrt-shop-stat-card__value {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
color: #1a1a2e;
|
|
}
|
|
|
|
.aksrt-shop-stat-card__label {
|
|
font-size: 13px;
|
|
color: #7a7f8a;
|
|
}
|
|
</style>
|