Files
order_site/apps/frontend/src/views/admin/AdminOrdersView.vue
T
2026-04-09 02:03:39 +08:00

310 lines
8.0 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.
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import AdminPaginationBar from '@/components/admin/AdminPaginationBar.vue'
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
import { fetchAdminOrders } from '@/services/admin'
import type { AdminOrderListItem, AdminPagination } from '@/types/admin'
import { adminPayStatusOptions } from '@/utils/admin-options'
import { formatAdminDateTime } from '@/utils/admin-time'
const loading = ref(true)
const errorMessage = ref('')
const platformOrderId = ref('')
const payStatus = ref('')
const skuCode = ref('')
const dateFrom = ref('')
const dateTo = ref('')
const items = ref<AdminOrderListItem[]>([])
const pagination = ref<AdminPagination>({
page: 1,
pageSize: 20,
total: 0,
})
async function loadOrders(page = pagination.value.page) {
loading.value = true
errorMessage.value = ''
try {
const response = await fetchAdminOrders({
page,
pageSize: pagination.value.pageSize,
platformOrderId: platformOrderId.value.trim(),
payStatus: payStatus.value.trim(),
skuCode: skuCode.value.trim(),
dateFrom: dateFrom.value.trim(),
dateTo: dateTo.value.trim(),
})
items.value = response.data.items
pagination.value = response.data.pagination
} catch (error) {
errorMessage.value = error instanceof Error ? error.message : '读取订单列表失败'
} finally {
loading.value = false
}
}
onMounted(loadOrders)
function resolveBuyerLabel(item: AdminOrderListItem) {
return item.buyerName || item.buyerId || '未识别'
}
function resolveContactLabel(item: AdminOrderListItem) {
return item.receiverContact || '未提供'
}
function resolveShopLabel(item: AdminOrderListItem) {
return item.shopName || item.shopId || '未识别店铺'
}
function resolveItemSummary(item: AdminOrderListItem) {
return item.itemSummary || '未识别商品'
}
</script>
<template>
<section class="admin-panel">
<header class="panel-header">
<div>
<h1>订单列表</h1>
<p>查看平台订单支付状态和对应任务数量</p>
</div>
</header>
<section class="filter-bar">
<input v-model="platformOrderId" class="text-input" placeholder="筛选订单号" />
<select v-model="payStatus" class="text-input select-input">
<option v-for="option in adminPayStatusOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
<input v-model="skuCode" class="text-input" placeholder="SKU,如 dnf-cdk-a" />
</section>
<section class="filter-bar">
<input v-model="dateFrom" class="text-input" type="date" placeholder="开始日期" />
<input v-model="dateTo" class="text-input" type="date" placeholder="结束日期" />
<el-button round type="primary" @click="() => loadOrders()">查询</el-button>
</section>
<p v-if="errorMessage" class="error-copy">{{ errorMessage }}</p>
<div v-if="loading" class="empty-block">订单列表加载中</div>
<div v-else class="table-card">
<table class="data-table">
<thead>
<tr>
<th>订单信息</th>
<th>平台 / 店铺</th>
<th>商品摘要</th>
<th>订单进度</th>
<th>金额 / 时间</th>
<th>任务进度</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" :key="item.orderId">
<td>
<div class="cell-stack">
<RouterLink :to="`/admin/orders/${item.orderId}`" class="primary-link">{{ item.platformOrderId }}</RouterLink>
<span class="cell-main">{{ resolveBuyerLabel(item) }}</span>
<span class="cell-sub">联系方式{{ resolveContactLabel(item) }}</span>
</div>
</td>
<td>
<div class="cell-stack">
<span class="cell-main">{{ item.platform }}</span>
<span class="cell-sub">{{ resolveShopLabel(item) }}</span>
</div>
</td>
<td>
<div class="cell-stack">
<span class="cell-main">{{ resolveItemSummary(item) }}</span>
<span class="cell-sub">{{ item.itemCount }} 合计 {{ item.totalQuantity }} </span>
</div>
</td>
<td>
<div class="status-stack">
<AdminStatusTag :status="item.payStatus" />
<AdminStatusTag :status="item.orderStatus" />
<AdminStatusTag :status="item.systemBindingStatus" />
<AdminStatusTag :status="item.userBindingStatus" />
</div>
</td>
<td>
<div class="cell-stack">
<span class="cell-main amount-text">{{ item.totalAmount }} {{ item.currency }}</span>
<span class="cell-sub">支付{{ formatAdminDateTime(item.paidAt) }}</span>
<span class="cell-sub">创建{{ formatAdminDateTime(item.createdAt) }}</span>
</div>
</td>
<td>
<div class="progress-stack">
<span
class="progress-chip"
:data-tone="item.completedBindingTaskCount > 0 ? (item.completedBindingTaskCount === item.taskCount ? 'success' : 'primary') : 'warning'"
>
完整绑定 {{ item.completedBindingTaskCount }}/{{ item.taskCount }}
</span>
<span
class="progress-chip"
:data-tone="item.systemBoundTaskCount > 0 ? (item.systemBoundTaskCount === item.taskCount ? 'success' : 'primary') : 'warning'"
>
系统绑定 {{ item.systemBoundTaskCount }}/{{ item.taskCount }}
</span>
</div>
</td>
</tr>
</tbody>
</table>
<AdminPaginationBar
:page="pagination.page"
:page-size="pagination.pageSize"
:total="pagination.total"
:loading="loading"
@change="loadOrders"
/>
</div>
</section>
</template>
<style scoped>
.status-stack {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
.cell-stack {
display: grid;
gap: 4px;
}
.primary-link,
.cell-main {
font-weight: 600;
color: #1d3555;
}
.cell-sub {
color: #64748b;
font-size: 13px;
}
.amount-text {
white-space: nowrap;
}
.progress-stack {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
.progress-chip {
display: inline-flex;
align-items: center;
min-height: 28px;
padding: 0 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
border: 1px solid transparent;
white-space: nowrap;
}
.progress-chip[data-tone='success'] {
color: #0f766e;
background: #ecfdf3;
border-color: #a6f4c5;
}
.progress-chip[data-tone='primary'] {
color: #175cd3;
background: #eff8ff;
border-color: #b2ddff;
}
.progress-chip[data-tone='warning'] {
color: #b54708;
background: #fffaeb;
border-color: #fedf89;
}
.data-table td {
vertical-align: top;
}
</style>
<style scoped>
.admin-panel {
display: grid;
gap: 16px;
}
.panel-header h1 {
margin: 0;
color: #1d3555;
}
.panel-header p {
margin: 8px 0 0;
color: #64748b;
}
.filter-bar {
display: flex;
gap: 12px;
align-items: center;
}
.text-input {
width: min(360px, 100%);
min-height: 42px;
padding: 0 14px;
border-radius: 14px;
border: 1px solid rgba(86, 108, 138, 0.18);
background: rgba(255, 255, 255, 0.94);
}
.select-input {
appearance: none;
}
.table-card,
.empty-block,
.error-copy {
padding: 18px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(86, 108, 138, 0.1);
}
.error-copy {
color: #b42318;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th,
.data-table td {
padding: 12px 10px;
text-align: left;
border-bottom: 1px solid rgba(86, 108, 138, 0.08);
color: #334155;
vertical-align: top;
}
.data-table th {
color: #64748b;
font-size: 13px;
}
</style>