340 lines
12 KiB
Vue
340 lines
12 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,
|
||
} 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'
|
||
}
|
||
</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>
|
||
普通订单 {{ moneyCent(dashboard.summary.normal_order_profit_amount_cent) }} + 线下提号
|
||
{{ moneyCent(dashboard.summary.pickup_profit_amount_cent) }}
|
||
</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_effective_settlement_amount_cent) }}</strong>
|
||
<small>钱包已入账 + 平台代管应付</small>
|
||
</div>
|
||
<div class="metric-card">
|
||
<span>区间待线下结算</span>
|
||
<strong>{{ moneyCent(dashboard.summary.offline_settlement_pending_amount_cent) }}</strong>
|
||
<small>{{ dashboard.summary.offline_settlement_pending_count }} 笔平台代管待处理</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?.mohong_summary" class="section-heading">
|
||
<div>
|
||
<h2>撞车商城</h2>
|
||
<p>商城交易流水单独统计,不计入平台利润。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="dashboard?.mohong_summary" class="metric-grid mohong-grid">
|
||
<div class="metric-card mohong-card">
|
||
<span>商城成交额</span>
|
||
<strong>{{ moneyCent(dashboard.mohong_summary.flow_amount_cent) }}</strong>
|
||
<small>{{ dashboard.mohong_summary.paid_order_count }} 笔成功订单</small>
|
||
</div>
|
||
<div class="metric-card mohong-card">
|
||
<span>商城退款</span>
|
||
<strong>{{ moneyCent(dashboard.mohong_summary.refund_amount_cent) }}</strong>
|
||
<small>已完成退款</small>
|
||
</div>
|
||
<div class="metric-card mohong-card">
|
||
<span>商城渠道净流入</span>
|
||
<strong>{{ moneyCent(dashboard.mohong_summary.net_amount_cent) }}</strong>
|
||
<small>成交额 - 已退款</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="dashboard?.disbursement_summary" class="section-heading">
|
||
<div>
|
||
<h2>资金出款</h2>
|
||
<p>已打款按所选日期统计,待处理金额为当前全部待办。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="dashboard?.disbursement_summary" class="metric-grid disbursement-grid">
|
||
<div class="metric-card disbursement-card">
|
||
<span>全部实际出款</span>
|
||
<strong>{{ moneyCent(dashboard.disbursement_summary.paid_amount_cent) }}</strong>
|
||
<small>{{ dashboard.disbursement_summary.paid_count }} 笔实际出款合计</small>
|
||
</div>
|
||
<div class="metric-card disbursement-card">
|
||
<span>平台代管打款</span>
|
||
<strong>{{
|
||
moneyCent(dashboard.disbursement_summary.offline_settlement_paid_amount_cent)
|
||
}}</strong>
|
||
<small>{{ dashboard.disbursement_summary.offline_settlement_paid_count }} 笔线下结算</small>
|
||
</div>
|
||
<div class="metric-card disbursement-card">
|
||
<span>用户提现打款</span>
|
||
<strong>{{ moneyCent(dashboard.disbursement_summary.withdrawal_paid_amount_cent) }}</strong>
|
||
<small>
|
||
{{ dashboard.disbursement_summary.withdrawal_paid_count }} 笔,钱包核销
|
||
{{ moneyCent(dashboard.disbursement_summary.withdrawal_amount_cent) }},手续费
|
||
{{ moneyCent(dashboard.disbursement_summary.withdrawal_fee_amount_cent) }}
|
||
</small>
|
||
</div>
|
||
<div class="metric-card disbursement-card">
|
||
<span>其他线下出款</span>
|
||
<strong>{{ moneyCent(dashboard.disbursement_summary.manual_paid_amount_cent) }}</strong>
|
||
<small>{{ dashboard.disbursement_summary.manual_paid_count }} 笔有效记账</small>
|
||
</div>
|
||
<div class="metric-card pending-card">
|
||
<span>当前待出款</span>
|
||
<strong>{{ moneyCent(dashboard.disbursement_summary.pending_payment_amount_cent) }}</strong>
|
||
<small>{{ dashboard.disbursement_summary.pending_payment_count }} 笔已进入打款阶段</small>
|
||
</div>
|
||
<div class="metric-card pending-card">
|
||
<span>代管待打款</span>
|
||
<strong>{{
|
||
moneyCent(dashboard.disbursement_summary.offline_settlement_pending_amount_cent)
|
||
}}</strong>
|
||
<small
|
||
>{{ dashboard.disbursement_summary.offline_settlement_pending_count }} 笔待处理</small
|
||
>
|
||
</div>
|
||
<div class="metric-card pending-card">
|
||
<span>提现待打款</span>
|
||
<strong>{{
|
||
moneyCent(dashboard.disbursement_summary.withdrawal_pending_amount_cent)
|
||
}}</strong>
|
||
<small>{{ dashboard.disbursement_summary.withdrawal_pending_count }} 笔已审核</small>
|
||
</div>
|
||
<div class="metric-card review-card">
|
||
<span>提现待审核</span>
|
||
<strong>{{
|
||
moneyCent(dashboard.disbursement_summary.withdrawal_review_amount_cent)
|
||
}}</strong>
|
||
<small>{{ dashboard.disbursement_summary.withdrawal_review_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>{{ moneyCent(dashboard.pickup_summary.profit_amount_cent) }}</strong>
|
||
<small>电商平台手工录入利润</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="120" />
|
||
<el-table-column label="哈夫币结算" width="120">
|
||
<template #default="{ row }">{{ row.settled_order_count }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="线下提号结算" width="140">
|
||
<template #default="{ row }">{{ row.pickup_completed_count }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="销售数量" width="110">
|
||
<template #default="{ row }">{{ row.sales_count }}</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.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="130">
|
||
<template #default="{ row }">{{ moneyCent(row.disbursement_paid_amount_cent) }}</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<p v-if="dashboard" class="generated-at">
|
||
数据生成时间:{{ formatDateTime(dashboard.generated_at) }}
|
||
</p>
|
||
</section>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.pickup-grid {
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.mohong-grid {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.section-heading {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: space-between;
|
||
margin: 4px 0 12px;
|
||
}
|
||
|
||
.section-heading h2 {
|
||
margin: 0;
|
||
color: #1f2937;
|
||
font-size: 18px;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.section-heading p {
|
||
margin: 5px 0 0;
|
||
color: #7f8aa3;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.disbursement-grid {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.disbursement-card {
|
||
border-left: 3px solid #0f766e;
|
||
}
|
||
|
||
.pending-card {
|
||
border-left: 3px solid #d97706;
|
||
}
|
||
|
||
.review-card {
|
||
border-left: 3px solid #64748b;
|
||
}
|
||
|
||
.pickup-card {
|
||
border-left: 3px solid #6366f1;
|
||
}
|
||
|
||
.mohong-card {
|
||
border-left: 3px solid #2563eb;
|
||
}
|
||
|
||
.generated-at {
|
||
margin: 12px 0 0;
|
||
color: #64748b;
|
||
font-size: 13px;
|
||
}
|
||
</style>
|