feat(pickup): 新增管理员线下提号功能

- 新建 admin_pickups 表独立于 rental_orders,不污染订单状态机与财务统计口径
- 提号中/已完成/已取消三态:创建锁定账号,完成时给卖家钱包加可用余额并下架 listing,取消解锁账号
- 完成时 listing 置 completed,复用现有 listingLockedForOwnerMutation 拦截再上架
- 管理端提号管理页(列表/创建/完成/取消/可提号账号搜索)+ 卖家端提号记录页
- 财务仪表盘新增线下提号独立统计块,与正常订单口径分离
- 钱包流水 label、状态标签、菜单入口同步补齐
- 优化卖家服务悬浮菜单为一行四个,尺寸与配色对齐买家服务
This commit is contained in:
yml2213
2026-07-03 19:04:35 +08:00
parent f5a1630a27
commit 9ef0843eb0
22 changed files with 1649 additions and 13 deletions
@@ -0,0 +1,132 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { Refresh, Search } from '@element-plus/icons-vue'
import { fetchSellerPickups, type SellerPickup } from '@/features/seller/api/sellerPickup'
import { formatCentWithSymbol } from '@/shared/utils/money'
import { formatDateTime } from '@/shared/utils/time'
import { pickupStatusLabel } from '@/shared/utils/statusLabels'
const loading = ref(false)
const items = ref<SellerPickup[]>([])
const total = ref(0)
const filters = reactive({ page: 1, page_size: 20, status: '' })
onMounted(loadList)
async function loadList() {
loading.value = true
try {
const result = await fetchSellerPickups(filters)
items.value = result.items
total.value = result.total
} finally {
loading.value = false
}
}
function handleSearch() {
filters.page = 1
loadList()
}
function handlePageChange(p: number) {
filters.page = p
loadList()
}
function handleSizeChange(s: number) {
filters.page_size = s
filters.page = 1
loadList()
}
function statusType(status: string) {
if (status === 'picking_up') return 'warning'
if (status === 'completed') return 'success'
if (status === 'cancelled') return 'info'
return ''
}
</script>
<template>
<section class="page">
<div class="page-header-row">
<div class="page-header">
<p class="eyebrow">Seller Pickup</p>
<h1>提号记录</h1>
<p>您的账号被管理员线下提号的记录完成后结算金额会进入钱包余额</p>
</div>
</div>
<div class="filter-bar">
<el-select v-model="filters.status" placeholder="状态" clearable style="width: 130px">
<el-option label="提号中" value="picking_up" />
<el-option label="已完成" value="completed" />
<el-option label="已取消" value="cancelled" />
</el-select>
<el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
<el-button :icon="Refresh" @click="loadList">刷新</el-button>
</div>
<el-table v-loading="loading" :data="items" class="table-panel">
<el-table-column prop="pickup_no" label="提号编号" width="190" />
<el-table-column prop="account_title" label="账号" min-width="180" />
<el-table-column label="区服/平台" width="150">
<template #default="{ row }">{{ row.server_region || '-' }} / {{ row.login_platform || '-' }}</template>
</el-table-column>
<el-table-column label="交易渠道" width="110">
<template #default="{ row }">{{ row.platform || '-' }}</template>
</el-table-column>
<el-table-column label="结算金额" width="140">
<template #default="{ row }">
<span v-if="row.status === 'completed'" class="amount-in">
{{ formatCentWithSymbol(row.settle_amount_cent) }}
</span>
<span v-else class="text-muted"></span>
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="statusType(row.status)" effect="light">
{{ pickupStatusLabel(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="时间" width="170">
<template #default="{ row }">
<span v-if="row.status === 'completed' && row.completed_at">
{{ formatDateTime(row.completed_at) }}
</span>
<span v-else>{{ formatDateTime(row.created_at) }}</span>
</template>
</el-table-column>
<el-table-column label="备注" min-width="160">
<template #default="{ row }">
<span v-if="row.status === 'completed' && row.complete_remark">{{ row.complete_remark }}</span>
<span v-else-if="row.remark">{{ row.remark }}</span>
<span v-else class="text-muted"></span>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrapper">
<el-pagination
:current-page="filters.page"
:page-size="filters.page_size"
:page-sizes="[20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
</section>
</template>
<style scoped>
.text-muted {
color: #94a3b8;
}
.amount-in {
color: #16a34a;
font-weight: 600;
}
</style>