- 新建 admin_pickups 表独立于 rental_orders,不污染订单状态机与财务统计口径 - 提号中/已完成/已取消三态:创建锁定账号,完成时给卖家钱包加可用余额并下架 listing,取消解锁账号 - 完成时 listing 置 completed,复用现有 listingLockedForOwnerMutation 拦截再上架 - 管理端提号管理页(列表/创建/完成/取消/可提号账号搜索)+ 卖家端提号记录页 - 财务仪表盘新增线下提号独立统计块,与正常订单口径分离 - 钱包流水 label、状态标签、菜单入口同步补齐 - 优化卖家服务悬浮菜单为一行四个,尺寸与配色对齐买家服务
223 lines
7.8 KiB
Vue
223 lines
7.8 KiB
Vue
<script setup lang="ts">
|
||
import { Refresh, Search } from '@element-plus/icons-vue'
|
||
import { computed, onMounted, reactive, ref } from 'vue'
|
||
|
||
import {
|
||
fetchFinanceDashboard,
|
||
SETTLEMENT_DIFF_THRESHOLD_CENT,
|
||
type FinanceDashboard,
|
||
type FinanceDailyItem,
|
||
} from '@/features/admin/api/adminFinance'
|
||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||
import { formatDateTime, formatInputDate } from '@/shared/utils/time'
|
||
|
||
const loading = ref(false)
|
||
const dashboard = ref<FinanceDashboard | null>(null)
|
||
const filters = reactive({
|
||
start_date: defaultStartDate(),
|
||
end_date: defaultEndDate(),
|
||
})
|
||
|
||
const dailyItems = computed(() => dashboard.value?.daily_items ?? [])
|
||
|
||
onMounted(loadDashboard)
|
||
|
||
async function loadDashboard() {
|
||
loading.value = true
|
||
try {
|
||
dashboard.value = await fetchFinanceDashboard(filters)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
function moneyCent(value: number) {
|
||
return formatCentWithSymbol(value)
|
||
}
|
||
|
||
function defaultStartDate() {
|
||
const date = new Date()
|
||
date.setDate(date.getDate() - 6)
|
||
return formatInputDate(date)
|
||
}
|
||
|
||
function defaultEndDate() {
|
||
return formatInputDate(new Date())
|
||
}
|
||
|
||
function diffType(value: number) {
|
||
return Math.abs(Number(value || 0)) >= SETTLEMENT_DIFF_THRESHOLD_CENT ? 'danger' : 'success'
|
||
}
|
||
|
||
function rowDiffClass(row: FinanceDailyItem) {
|
||
return Math.abs(Number(row.settlement_diff_amount_cent || 0)) >= SETTLEMENT_DIFF_THRESHOLD_CENT
|
||
? 'amount-danger'
|
||
: ''
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<section class="page">
|
||
<div class="page-header-row">
|
||
<div class="page-header">
|
||
<p class="eyebrow">Finance Dashboard</p>
|
||
<h1>财务仪表盘</h1>
|
||
<p>按天查看第三方收款退款、平台收入、号主入账和结算差异。</p>
|
||
</div>
|
||
<div class="toolbar-actions">
|
||
<el-date-picker
|
||
v-model="filters.start_date"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="开始日期"
|
||
/>
|
||
<el-date-picker
|
||
v-model="filters.end_date"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="结束日期"
|
||
/>
|
||
<el-button type="primary" :icon="Search" :loading="loading" @click="loadDashboard">
|
||
查询
|
||
</el-button>
|
||
<el-button :icon="Refresh" :loading="loading" @click="loadDashboard">刷新</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="dashboard" class="metric-grid">
|
||
<div class="metric-card">
|
||
<span>总流水</span>
|
||
<strong>{{ moneyCent(dashboard.summary.total_flow_amount_cent) }}</strong>
|
||
<small>{{ dashboard.summary.successful_pay_count }} 笔成功收款</small>
|
||
</div>
|
||
<div class="metric-card">
|
||
<span>总退款</span>
|
||
<strong>{{ moneyCent(dashboard.summary.total_refund_amount_cent) }}</strong>
|
||
<small
|
||
>{{ dashboard.summary.full_refund_count }} 笔全额 /
|
||
{{ dashboard.summary.partial_refund_count }} 笔部分</small
|
||
>
|
||
</div>
|
||
<div class="metric-card">
|
||
<span>渠道净流入</span>
|
||
<strong>{{ moneyCent(dashboard.summary.channel_net_amount_cent) }}</strong>
|
||
<small>成功收款 - 成功退款</small>
|
||
</div>
|
||
<div class="metric-card">
|
||
<span>平台收入</span>
|
||
<strong>{{ moneyCent(dashboard.summary.platform_income_amount_cent) }}</strong>
|
||
<small>{{ dashboard.summary.settled_order_count }} 个已结算订单</small>
|
||
</div>
|
||
<div class="metric-card">
|
||
<span>号主应得</span>
|
||
<strong>{{ moneyCent(dashboard.summary.owner_should_income_amount_cent) }}</strong>
|
||
<small>结账单口径</small>
|
||
</div>
|
||
<div class="metric-card">
|
||
<span>号主实际入账</span>
|
||
<strong>{{ moneyCent(dashboard.summary.owner_wallet_income_amount_cent) }}</strong>
|
||
<small>钱包流水口径</small>
|
||
</div>
|
||
<div class="metric-card">
|
||
<span>退款中</span>
|
||
<strong>{{ moneyCent(dashboard.summary.pending_refund_amount_cent) }}</strong>
|
||
<small>{{ dashboard.summary.pending_refund_count }} 笔待回执</small>
|
||
</div>
|
||
<div class="metric-card">
|
||
<span>结算差异</span>
|
||
<strong>
|
||
<el-tag :type="diffType(dashboard.summary.settlement_diff_amount_cent)">
|
||
{{ moneyCent(dashboard.summary.settlement_diff_amount_cent) }}
|
||
</el-tag>
|
||
</strong>
|
||
<small>{{ dashboard.summary.financial_exception_count }} 个异常订单</small>
|
||
</div>
|
||
<div class="metric-card">
|
||
<span>预计收入</span>
|
||
<strong>{{ moneyCent(dashboard.summary.estimated_income_amount_cent) }}</strong>
|
||
<small>{{ dashboard.summary.pending_settle_order_count }} 个待结算订单</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="dashboard?.pickup_summary" class="metric-grid pickup-grid">
|
||
<div class="metric-card pickup-card">
|
||
<span>线下提号结算</span>
|
||
<strong>{{ moneyCent(dashboard.pickup_summary.settled_amount_cent) }}</strong>
|
||
<small>{{ dashboard.pickup_summary.completed_count }} 笔已完成</small>
|
||
</div>
|
||
<div class="metric-card pickup-card">
|
||
<span>提号中</span>
|
||
<strong>{{ dashboard.pickup_summary.in_progress_count }}</strong>
|
||
<small>进行中笔数</small>
|
||
</div>
|
||
</div>
|
||
|
||
<el-table v-loading="loading" class="table-panel" :data="dailyItems">
|
||
<el-table-column prop="date" label="日期" width="130" />
|
||
<el-table-column label="总流水" width="130">
|
||
<template #default="{ row }">{{ moneyCent(row.total_flow_amount_cent) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="总退款" width="130">
|
||
<template #default="{ row }">{{ moneyCent(row.total_refund_amount_cent) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="渠道净流入" width="140">
|
||
<template #default="{ row }">{{ moneyCent(row.channel_net_amount_cent) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="平台收入" width="130">
|
||
<template #default="{ row }">{{ moneyCent(row.platform_income_amount_cent) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="号主应得" width="130">
|
||
<template #default="{ row }">{{ moneyCent(row.owner_should_income_amount_cent) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="号主入账" width="130">
|
||
<template #default="{ row }">{{ moneyCent(row.owner_wallet_income_amount_cent) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="结算差异" width="130">
|
||
<template #default="{ row }">
|
||
<span :class="rowDiffClass(row)">{{ moneyCent(row.settlement_diff_amount_cent) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="预计收入" width="130">
|
||
<template #default="{ row }">{{ moneyCent(row.estimated_income_amount_cent) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="销售数量" width="110">
|
||
<template #default="{ row }">{{ row.successful_pay_count }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="全额退款" width="100">
|
||
<template #default="{ row }">{{ row.full_refund_count }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="部分退款" width="100">
|
||
<template #default="{ row }">{{ row.partial_refund_count }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="结算数量" width="110">
|
||
<template #default="{ row }">{{ row.settled_order_count }}</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<p v-if="dashboard" class="generated-at">
|
||
数据生成时间:{{ formatDateTime(dashboard.generated_at) }}
|
||
</p>
|
||
</section>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.amount-danger {
|
||
color: #dc2626;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.pickup-grid {
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.pickup-card {
|
||
border-left: 3px solid #6366f1;
|
||
}
|
||
|
||
.generated-at {
|
||
margin: 12px 0 0;
|
||
color: #64748b;
|
||
font-size: 13px;
|
||
}
|
||
</style>
|