1561 lines
40 KiB
Vue
1561 lines
40 KiB
Vue
<script setup lang="ts">
|
||
import { computed } from 'vue'
|
||
import {
|
||
Bell,
|
||
Bottom,
|
||
ChatDotRound,
|
||
DataLine,
|
||
Document,
|
||
Grid,
|
||
Loading,
|
||
Operation,
|
||
Refresh,
|
||
ScaleToOriginal,
|
||
ShoppingBag,
|
||
Shop,
|
||
Tickets,
|
||
Top,
|
||
User,
|
||
Van,
|
||
Wallet,
|
||
Warning,
|
||
} from '@element-plus/icons-vue'
|
||
import {
|
||
fetchAdminDashboard,
|
||
type AdminDashboard,
|
||
type ListingChannelDayStats,
|
||
type ListingDayStats,
|
||
type ListingUploaderDayStats,
|
||
} from '@/features/admin/api/adminDashboard'
|
||
import { useAdminTable } from '@/features/admin/composables/useAdminTable'
|
||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||
import { adminPath } from '@/shared/utils/adminPath'
|
||
import { disputeStatusLabel, orderStatusLabel } from '@/shared/utils/statusLabels'
|
||
import { formatDateTime } from '@/shared/utils/time'
|
||
|
||
const {
|
||
loading,
|
||
error,
|
||
data: dashboard,
|
||
load: loadDashboard,
|
||
} = useAdminTable<AdminDashboard>({
|
||
fetchFn: fetchAdminDashboard,
|
||
})
|
||
|
||
const listingTrend = computed(() => dashboard.value?.listing_daily?.trend ?? [])
|
||
const listingChannelTrend = computed(() => dashboard.value?.listing_daily?.channel_trend ?? [])
|
||
const listingUploaderTrend = computed(() => dashboard.value?.listing_daily?.uploader_trend ?? [])
|
||
const listingToday = computed(
|
||
() =>
|
||
dashboard.value?.listing_daily?.today ?? {
|
||
date: '',
|
||
published_count: 0,
|
||
active_offline_count: 0,
|
||
trade_leave_count: 0,
|
||
}
|
||
)
|
||
const channelDisplayOrder = ['咸鱼', '淘宝', '京东', 'QQ', '微信', '自定义', '站内发布', '未填写']
|
||
const listingTodayChannels = computed<ListingChannelDayStats[]>(() => {
|
||
const date = listingToday.value.date
|
||
const rows = dashboard.value?.listing_daily?.today_channels ?? []
|
||
const byChannel = new Map(rows.map(row => [row.source_channel || '未填写', row]))
|
||
const ordered = channelDisplayOrder.map(channel => ({
|
||
date,
|
||
source_channel: channel,
|
||
published_count: byChannel.get(channel)?.published_count ?? 0,
|
||
active_offline_count: byChannel.get(channel)?.active_offline_count ?? 0,
|
||
trade_leave_count: byChannel.get(channel)?.trade_leave_count ?? 0,
|
||
}))
|
||
const extras = rows.filter(row => !channelDisplayOrder.includes(row.source_channel || '未填写'))
|
||
return [...ordered, ...extras]
|
||
})
|
||
const listingTodayUploaders = computed<ListingUploaderDayStats[]>(() => {
|
||
const date = listingToday.value.date
|
||
return (dashboard.value?.listing_daily?.today_uploaders ?? [])
|
||
.filter(row => row.date === date)
|
||
.sort(
|
||
(a, b) => b.upload_count - a.upload_count || a.uploader_name.localeCompare(b.uploader_name)
|
||
)
|
||
})
|
||
|
||
function leaveCount(row: ListingChannelDayStats | ListingDayStats) {
|
||
return Number(row.active_offline_count || 0) + Number(row.trade_leave_count || 0)
|
||
}
|
||
|
||
function trendMax(getter: (row: ListingDayStats) => number) {
|
||
const values = listingTrend.value.map(getter)
|
||
return Math.max(1, ...values, 0)
|
||
}
|
||
|
||
function barHeight(value: number, max: number) {
|
||
if (max <= 0) return '8%'
|
||
return `${Math.max(8, Math.round((value / max) * 100))}%`
|
||
}
|
||
|
||
function shortDate(date: string) {
|
||
if (!date || date.length < 10) return date
|
||
return date.slice(5)
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="finance-page-container">
|
||
<!-- 头部卡片 -->
|
||
<header class="page-header-card">
|
||
<div class="header-main">
|
||
<div class="title-group">
|
||
<div class="eyebrow-tag">
|
||
<el-icon><DataLine /></el-icon>
|
||
<span>DASHBOARD</span>
|
||
</div>
|
||
<h1 class="page-title">后台仪表盘</h1>
|
||
<p class="page-subtitle">实时监控平台运营核心数据,洞察商品流转趋势与待办事项。</p>
|
||
</div>
|
||
|
||
<div class="toolbar-actions">
|
||
<el-button :icon="Refresh" :loading="loading" @click="loadDashboard">
|
||
刷新数据
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- 加载与错误状态 -->
|
||
<div v-if="loading && !dashboard" class="state-card loading-state">
|
||
<el-icon class="is-loading"><Loading /></el-icon>
|
||
<span>加载仪表盘数据中...</span>
|
||
</div>
|
||
|
||
<div v-else-if="error && !dashboard" class="state-card error-state">
|
||
<el-icon><Warning /></el-icon>
|
||
<span>{{ error }}</span>
|
||
<el-button type="primary" @click="loadDashboard">重试</el-button>
|
||
</div>
|
||
|
||
<!-- 核心指标卡片 -->
|
||
<section v-if="dashboard" class="summary-metric-grid">
|
||
<div class="metric-card metric-card--users">
|
||
<div class="metric-icon-wrap indigo">
|
||
<el-icon><User /></el-icon>
|
||
</div>
|
||
<div class="metric-info">
|
||
<span class="metric-label">用户总数</span>
|
||
<strong class="metric-val indigo-val">{{ dashboard.metrics.total_users }}</strong>
|
||
<span class="metric-sub-pill info">实名 {{ dashboard.metrics.verified_users }} 人</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="metric-card metric-card--listings">
|
||
<div class="metric-icon-wrap emerald">
|
||
<el-icon><ShoppingBag /></el-icon>
|
||
</div>
|
||
<div class="metric-info">
|
||
<span class="metric-label">发布商品</span>
|
||
<strong class="metric-val emerald-val">{{ dashboard.metrics.total_listings }}</strong>
|
||
<span class="metric-sub-pill success"
|
||
>已上架 {{ dashboard.metrics.published_listings }} 个</span
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="metric-card metric-card--orders">
|
||
<div class="metric-icon-wrap amber">
|
||
<el-icon><Document /></el-icon>
|
||
</div>
|
||
<div class="metric-info">
|
||
<span class="metric-label">订单总数</span>
|
||
<strong class="metric-val amber-val">{{ dashboard.metrics.total_orders }}</strong>
|
||
<span class="metric-sub-pill warning"
|
||
>使用中 {{ dashboard.metrics.renting_orders }} 个</span
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="metric-card metric-card--flow">
|
||
<div class="metric-icon-wrap rose">
|
||
<el-icon><Wallet /></el-icon>
|
||
</div>
|
||
<div class="metric-info">
|
||
<span class="metric-label">今日流水</span>
|
||
<strong class="metric-val rose-val">
|
||
{{ formatCentWithSymbol(dashboard.metrics.today_ledger_amount_cent) }}
|
||
</strong>
|
||
<span class="metric-sub-pill danger"
|
||
>今日订单 {{ dashboard.metrics.today_orders }} 个</span
|
||
>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 账号上下架:今日三组 + 近 7 日趋势 -->
|
||
<section v-if="dashboard" class="section-card">
|
||
<div class="section-header-row">
|
||
<div class="section-header-title">
|
||
<el-icon class="header-icon"><Shop /></el-icon>
|
||
<span>账号上下架趋势</span>
|
||
<span class="header-badge"
|
||
>按北京时间自然日 · 近 {{ dashboard.listing_daily.days }} 日</span
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 今日指标与柱状趋势 -->
|
||
<div class="listing-daily-grid">
|
||
<div class="listing-stat-card tone-publish">
|
||
<div class="listing-stat-head">
|
||
<div class="listing-stat-icon">
|
||
<el-icon><Top /></el-icon>
|
||
</div>
|
||
<div class="listing-stat-meta">
|
||
<span>今日上架</span>
|
||
<strong>{{ listingToday.published_count }}</strong>
|
||
</div>
|
||
</div>
|
||
<p class="listing-stat-hint">免审发布 / 审核通过 / 取消订单恢复上架</p>
|
||
<div class="mini-bars">
|
||
<div
|
||
v-for="row in listingTrend"
|
||
:key="`pub-${row.date}`"
|
||
class="mini-bar-col"
|
||
:title="`${row.date}:${row.published_count}`"
|
||
>
|
||
<div
|
||
class="mini-bar publish"
|
||
:style="{
|
||
height: barHeight(
|
||
row.published_count,
|
||
trendMax(r => r.published_count)
|
||
),
|
||
}"
|
||
/>
|
||
<em>{{ shortDate(row.date) }}</em>
|
||
<b>{{ row.published_count }}</b>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="listing-stat-card tone-offline">
|
||
<div class="listing-stat-head">
|
||
<div class="listing-stat-icon">
|
||
<el-icon><Bottom /></el-icon>
|
||
</div>
|
||
<div class="listing-stat-meta">
|
||
<span>今日主动下架</span>
|
||
<strong>{{ listingToday.active_offline_count }}</strong>
|
||
</div>
|
||
</div>
|
||
<p class="listing-stat-hint">号主手动下架 + 后台管理下架</p>
|
||
<div class="mini-bars">
|
||
<div
|
||
v-for="row in listingTrend"
|
||
:key="`off-${row.date}`"
|
||
class="mini-bar-col"
|
||
:title="`${row.date}:${row.active_offline_count}`"
|
||
>
|
||
<div
|
||
class="mini-bar offline"
|
||
:style="{
|
||
height: barHeight(
|
||
row.active_offline_count,
|
||
trendMax(r => r.active_offline_count)
|
||
),
|
||
}"
|
||
/>
|
||
<em>{{ shortDate(row.date) }}</em>
|
||
<b>{{ row.active_offline_count }}</b>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="listing-stat-card tone-trade">
|
||
<div class="listing-stat-head">
|
||
<div class="listing-stat-icon">
|
||
<el-icon><Van /></el-icon>
|
||
</div>
|
||
<div class="listing-stat-meta">
|
||
<span>今日交易离架</span>
|
||
<strong>{{ listingToday.trade_leave_count }}</strong>
|
||
</div>
|
||
</div>
|
||
<p class="listing-stat-hint">租出 / 售出完成 / 封存 / 订单仲裁归档</p>
|
||
<div class="mini-bars">
|
||
<div
|
||
v-for="row in listingTrend"
|
||
:key="`trade-${row.date}`"
|
||
class="mini-bar-col"
|
||
:title="`${row.date}:${row.trade_leave_count}`"
|
||
>
|
||
<div
|
||
class="mini-bar trade"
|
||
:style="{
|
||
height: barHeight(
|
||
row.trade_leave_count,
|
||
trendMax(r => r.trade_leave_count)
|
||
),
|
||
}"
|
||
/>
|
||
<em>{{ shortDate(row.date) }}</em>
|
||
<b>{{ row.trade_leave_count }}</b>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 渠道总览 -->
|
||
<div class="channel-summary-title">今日各渠道上下架汇总</div>
|
||
<div class="listing-channel-summary">
|
||
<div
|
||
v-for="row in listingTodayChannels"
|
||
:key="`today-channel-${row.source_channel}`"
|
||
class="listing-channel-item"
|
||
>
|
||
<div class="listing-channel-item-head">
|
||
<span class="channel-name">{{ row.source_channel || '未填写' }}</span>
|
||
<strong class="channel-total">{{ row.published_count + leaveCount(row) }}</strong>
|
||
</div>
|
||
<div class="listing-channel-counts">
|
||
<span class="count-tag publish">上架 {{ row.published_count }}</span>
|
||
<span class="count-tag offline">主动下架 {{ row.active_offline_count }}</span>
|
||
<span class="count-tag trade">交易离架 {{ row.trade_leave_count }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="listingTodayUploaders.length" class="listing-uploader-block">
|
||
<div class="channel-summary-title">今日外部上传账号统计</div>
|
||
<div class="listing-uploader-summary">
|
||
<div
|
||
v-for="row in listingTodayUploaders"
|
||
:key="`today-uploader-${row.uploader_id}-${row.uploader_name}`"
|
||
class="listing-uploader-item"
|
||
>
|
||
<span class="uploader-name">{{ row.uploader_name || '未填写' }}</span>
|
||
<strong class="uploader-total">{{ row.upload_count }}</strong>
|
||
<span class="uploader-unit">个账号</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 7日上下架趋势明细表 -->
|
||
<div class="table-container trend-table-wrap">
|
||
<el-table class="custom-enterprise-table" :data="listingTrend" size="small" stripe>
|
||
<el-table-column prop="date" label="统计日期" min-width="120" />
|
||
<el-table-column prop="published_count" label="上架量" width="120" align="center">
|
||
<template #default="{ row }">
|
||
<span class="trend-val text-emerald">+{{ row.published_count }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="active_offline_count" label="主动下架" width="130" align="center">
|
||
<template #default="{ row }">
|
||
<span class="trend-val text-amber">-{{ row.active_offline_count }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="trade_leave_count" label="交易离架" width="130" align="center">
|
||
<template #default="{ row }">
|
||
<span class="trend-val text-blue">-{{ row.trade_leave_count }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="合计离架" width="120" align="center">
|
||
<template #default="{ row }">
|
||
<strong class="trend-val text-slate">{{
|
||
row.active_offline_count + row.trade_leave_count
|
||
}}</strong>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<!-- 近 7 日渠道明细表 -->
|
||
<div v-if="listingChannelTrend.length" class="listing-channel-block">
|
||
<div class="channel-summary-title">近 7 日各渠道每日明细</div>
|
||
<div class="table-container">
|
||
<el-table class="custom-enterprise-table" :data="listingChannelTrend" size="small" stripe>
|
||
<el-table-column prop="date" label="统计日期" min-width="120" />
|
||
<el-table-column prop="source_channel" label="渠道来源" min-width="140">
|
||
<template #default="{ row }">
|
||
<el-tag size="small" effect="plain" class="channel-badge">
|
||
{{ row.source_channel || '-' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="published_count" label="上架" width="110" align="center">
|
||
<template #default="{ row }">
|
||
<span class="trend-val text-emerald">+{{ row.published_count }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="active_offline_count"
|
||
label="主动下架"
|
||
width="120"
|
||
align="center"
|
||
>
|
||
<template #default="{ row }">
|
||
<span class="trend-val text-amber">-{{ row.active_offline_count }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="trade_leave_count" label="交易离架" width="120" align="center">
|
||
<template #default="{ row }">
|
||
<span class="trend-val text-blue">-{{ row.trade_leave_count }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="合计离架" width="110" align="center">
|
||
<template #default="{ row }">
|
||
<strong class="trend-val text-slate">{{ leaveCount(row) }}</strong>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="listingUploaderTrend.length" class="listing-uploader-block">
|
||
<div class="channel-summary-title">近 7 日客服上传账号明细</div>
|
||
<div class="table-container">
|
||
<el-table
|
||
class="custom-enterprise-table"
|
||
:data="listingUploaderTrend"
|
||
size="small"
|
||
stripe
|
||
>
|
||
<el-table-column prop="date" label="统计日期" min-width="120" />
|
||
<el-table-column prop="uploader_name" label="客服" min-width="140">
|
||
<template #default="{ row }">{{ row.uploader_name || '未填写' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="upload_count" label="上传账号数" width="130" align="center" />
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 待处理事项 & 快捷入口 -->
|
||
<div v-if="dashboard" class="dashboard-panels-grid">
|
||
<!-- 待处理事项 -->
|
||
<section class="section-card panel-card">
|
||
<div class="panel-header-row">
|
||
<div class="panel-header-title">
|
||
<el-icon class="panel-icon amber-icon"><Bell /></el-icon>
|
||
<span>待处理事项</span>
|
||
</div>
|
||
<span
|
||
class="pending-total-badge"
|
||
:class="{
|
||
'has-pending':
|
||
dashboard.pending.disputes +
|
||
dashboard.pending.listing_reviews +
|
||
dashboard.pending.pending_handoffs +
|
||
dashboard.pending.pending_return_confirms >
|
||
0,
|
||
}"
|
||
>
|
||
{{
|
||
dashboard.pending.disputes +
|
||
dashboard.pending.listing_reviews +
|
||
dashboard.pending.pending_handoffs +
|
||
dashboard.pending.pending_return_confirms
|
||
}}
|
||
项待办
|
||
</span>
|
||
</div>
|
||
|
||
<div class="pending-list-group">
|
||
<RouterLink class="pending-item-card" :to="adminPath('disputes')">
|
||
<div class="pending-info">
|
||
<span class="pending-label">待仲裁申诉</span>
|
||
<span class="pending-desc">需要人工介入处理的用户争议</span>
|
||
</div>
|
||
<span
|
||
class="pending-badge"
|
||
:class="dashboard.pending.disputes > 0 ? 'badge-urgent' : 'badge-zero'"
|
||
>
|
||
{{ dashboard.pending.disputes }}
|
||
</span>
|
||
</RouterLink>
|
||
|
||
<RouterLink class="pending-item-card" :to="adminPath('listings/review')">
|
||
<div class="pending-info">
|
||
<span class="pending-label">待审核商品</span>
|
||
<span class="pending-desc">卖家新提交上架的账号资产审核</span>
|
||
</div>
|
||
<span
|
||
class="pending-badge"
|
||
:class="dashboard.pending.listing_reviews > 0 ? 'badge-urgent' : 'badge-zero'"
|
||
>
|
||
{{ dashboard.pending.listing_reviews }}
|
||
</span>
|
||
</RouterLink>
|
||
|
||
<div class="pending-item-card static-card">
|
||
<div class="pending-info">
|
||
<span class="pending-label">待交接订单</span>
|
||
<span class="pending-desc">等待卖家确认交接发货</span>
|
||
</div>
|
||
<span
|
||
class="pending-badge"
|
||
:class="dashboard.pending.pending_handoffs > 0 ? 'badge-urgent' : 'badge-zero'"
|
||
>
|
||
{{ dashboard.pending.pending_handoffs }}
|
||
</span>
|
||
</div>
|
||
|
||
<div class="pending-item-card static-card">
|
||
<div class="pending-info">
|
||
<span class="pending-label">待结账确认</span>
|
||
<span class="pending-desc">租期结束等待双方确认结算</span>
|
||
</div>
|
||
<span
|
||
class="pending-badge"
|
||
:class="dashboard.pending.pending_return_confirms > 0 ? 'badge-urgent' : 'badge-zero'"
|
||
>
|
||
{{ dashboard.pending.pending_return_confirms }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 快捷入口 -->
|
||
<section class="section-card panel-card">
|
||
<div class="panel-header-row">
|
||
<div class="panel-header-title">
|
||
<el-icon class="panel-icon blue-icon"><Grid /></el-icon>
|
||
<span>快捷业务导航</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="quick-nav-grid">
|
||
<RouterLink class="quick-tile" :to="adminPath('users')">
|
||
<div class="quick-icon-wrap indigo">
|
||
<el-icon><User /></el-icon>
|
||
</div>
|
||
<span class="quick-title">用户管理</span>
|
||
</RouterLink>
|
||
|
||
<RouterLink class="quick-tile" :to="adminPath('orders')">
|
||
<div class="quick-icon-wrap blue">
|
||
<el-icon><Tickets /></el-icon>
|
||
</div>
|
||
<span class="quick-title">订单管理</span>
|
||
</RouterLink>
|
||
|
||
<RouterLink class="quick-tile" :to="adminPath('disputes')">
|
||
<div class="quick-icon-wrap rose">
|
||
<el-icon><ScaleToOriginal /></el-icon>
|
||
</div>
|
||
<span class="quick-title">仲裁中心</span>
|
||
</RouterLink>
|
||
|
||
<RouterLink class="quick-tile" :to="adminPath('listings')">
|
||
<div class="quick-icon-wrap emerald">
|
||
<el-icon><Shop /></el-icon>
|
||
</div>
|
||
<span class="quick-title">商品管理</span>
|
||
</RouterLink>
|
||
|
||
<RouterLink class="quick-tile" :to="adminPath('chats')">
|
||
<div class="quick-icon-wrap teal">
|
||
<el-icon><ChatDotRound /></el-icon>
|
||
</div>
|
||
<span class="quick-title">客服群聊</span>
|
||
</RouterLink>
|
||
|
||
<RouterLink class="quick-tile" :to="adminPath('system-configs')">
|
||
<div class="quick-icon-wrap slate">
|
||
<el-icon><Operation /></el-icon>
|
||
</div>
|
||
<span class="quick-title">系统配置</span>
|
||
</RouterLink>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<!-- 最近订单表格 -->
|
||
<section v-if="dashboard" class="section-card table-section-card">
|
||
<div class="table-card-header">
|
||
<div class="table-header-title">
|
||
<el-icon class="header-icon"><Tickets /></el-icon>
|
||
<span>最近订单动态</span>
|
||
</div>
|
||
<RouterLink :to="adminPath('orders')" class="view-all-link">查看全部订单 →</RouterLink>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<el-table class="custom-enterprise-table" :data="dashboard.recent_orders" stripe>
|
||
<el-table-column prop="order_no" label="订单号" min-width="210">
|
||
<template #default="{ row }">
|
||
<RouterLink :to="adminPath(`orders/${row.id}`)" class="order-link">
|
||
{{ row.order_no }}
|
||
</RouterLink>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="title" label="商品名称" min-width="190" show-overflow-tooltip />
|
||
<el-table-column label="订单状态" width="120" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
:type="
|
||
row.status === 'completed'
|
||
? 'success'
|
||
: row.status === 'renting'
|
||
? 'primary'
|
||
: 'warning'
|
||
"
|
||
size="small"
|
||
effect="plain"
|
||
class="status-tag"
|
||
>
|
||
{{ orderStatusLabel(row.status) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="rent_amount_cent" label="租金金额" min-width="130" align="right">
|
||
<template #default="{ row }">
|
||
<strong class="amount-val">{{ formatCentWithSymbol(row.rent_amount_cent) }}</strong>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="创建时间" min-width="170">
|
||
<template #default="{ row }">
|
||
<span class="time-cell">{{ formatDateTime(row.created_at) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 最近申诉表格 -->
|
||
<section
|
||
v-if="dashboard && dashboard.recent_disputes.length > 0"
|
||
class="section-card table-section-card"
|
||
>
|
||
<div class="table-card-header">
|
||
<div class="table-header-title">
|
||
<el-icon class="header-icon rose-icon"><Warning /></el-icon>
|
||
<span>最近申诉记录</span>
|
||
</div>
|
||
<RouterLink :to="adminPath('disputes')" class="view-all-link">前往仲裁中心 →</RouterLink>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<el-table class="custom-enterprise-table" :data="dashboard.recent_disputes" stripe>
|
||
<el-table-column prop="order_no" label="订单号" min-width="210">
|
||
<template #default="{ row }">
|
||
<span class="mono-code">{{ row.order_no }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="title" label="商品名称" min-width="190" show-overflow-tooltip />
|
||
<el-table-column prop="type" label="申诉类型" width="140">
|
||
<template #default="{ row }">
|
||
<span class="dispute-type">{{ row.type }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" width="120" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
:type="row.status === 'resolved' ? 'success' : 'danger'"
|
||
size="small"
|
||
effect="plain"
|
||
class="status-tag"
|
||
>
|
||
{{ disputeStatusLabel(row.status) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="创建时间" min-width="170">
|
||
<template #default="{ row }">
|
||
<span class="time-cell">{{ formatDateTime(row.created_at) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="90" align="center">
|
||
<template #default>
|
||
<RouterLink :to="adminPath('disputes')">
|
||
<el-button size="small" type="primary" link>处理</el-button>
|
||
</RouterLink>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.finance-page-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
padding: 0 4px 32px;
|
||
}
|
||
|
||
/* ================= 头部卡片 ================= */
|
||
.page-header-card {
|
||
background: #ffffff;
|
||
border-radius: 16px;
|
||
padding: 24px 28px;
|
||
box-shadow:
|
||
0 1px 3px rgba(15, 23, 42, 0.04),
|
||
0 4px 12px rgba(15, 23, 42, 0.02);
|
||
border: 1px solid rgba(226, 232, 240, 0.8);
|
||
}
|
||
|
||
.header-main {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.title-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
|
||
.eyebrow-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.8px;
|
||
color: #3b82f6;
|
||
background: #eff6ff;
|
||
padding: 3px 10px;
|
||
border-radius: 20px;
|
||
width: fit-content;
|
||
}
|
||
|
||
.page-title {
|
||
margin: 0;
|
||
font-size: 26px;
|
||
font-weight: 800;
|
||
color: #0f172a;
|
||
letter-spacing: -0.5px;
|
||
}
|
||
|
||
.page-subtitle {
|
||
margin: 0;
|
||
font-size: 13.5px;
|
||
color: #64748b;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.toolbar-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
/* ================= 状态卡片 ================= */
|
||
.state-card {
|
||
background: #ffffff;
|
||
border-radius: 16px;
|
||
padding: 48px 24px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 16px;
|
||
border: 1px solid rgba(226, 232, 240, 0.8);
|
||
}
|
||
|
||
.loading-state {
|
||
color: #64748b;
|
||
}
|
||
|
||
.loading-state .el-icon {
|
||
font-size: 36px;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.error-state {
|
||
color: #e11d48;
|
||
}
|
||
|
||
.error-state .el-icon {
|
||
font-size: 36px;
|
||
}
|
||
|
||
/* ================= 核心指标卡片 ================= */
|
||
.summary-metric-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.metric-card {
|
||
background: #ffffff;
|
||
border-radius: 16px;
|
||
padding: 20px 22px;
|
||
border: 1px solid rgba(226, 232, 240, 0.8);
|
||
box-shadow:
|
||
0 1px 3px rgba(15, 23, 42, 0.04),
|
||
0 4px 12px rgba(15, 23, 42, 0.02);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
transition:
|
||
transform 0.2s ease,
|
||
box-shadow 0.2s ease;
|
||
}
|
||
|
||
.metric-card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
|
||
}
|
||
|
||
.metric-card::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 4px;
|
||
}
|
||
|
||
.metric-card--users::before {
|
||
background: linear-gradient(90deg, #6366f1, #818cf8);
|
||
}
|
||
|
||
.metric-card--listings::before {
|
||
background: linear-gradient(90deg, #10b981, #34d399);
|
||
}
|
||
|
||
.metric-card--orders::before {
|
||
background: linear-gradient(90deg, #f59e0b, #fbbf24);
|
||
}
|
||
|
||
.metric-card--flow::before {
|
||
background: linear-gradient(90deg, #f43f5e, #fb7185);
|
||
}
|
||
|
||
.metric-icon-wrap {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 22px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.metric-icon-wrap.indigo {
|
||
background: #eef2ff;
|
||
color: #6366f1;
|
||
}
|
||
|
||
.metric-icon-wrap.emerald {
|
||
background: #ecfdf5;
|
||
color: #10b981;
|
||
}
|
||
|
||
.metric-icon-wrap.amber {
|
||
background: #fffbeb;
|
||
color: #f59e0b;
|
||
}
|
||
|
||
.metric-icon-wrap.rose {
|
||
background: #fff1f2;
|
||
color: #f43f5e;
|
||
}
|
||
|
||
.metric-icon-wrap.blue {
|
||
background: #eff6ff;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.metric-icon-wrap.teal {
|
||
background: #f0fdfa;
|
||
color: #0d9488;
|
||
}
|
||
|
||
.metric-icon-wrap.slate {
|
||
background: #f1f5f9;
|
||
color: #64748b;
|
||
}
|
||
|
||
.metric-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.metric-label {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: #64748b;
|
||
}
|
||
|
||
.metric-val {
|
||
font-size: 26px;
|
||
font-weight: 800;
|
||
color: #0f172a;
|
||
letter-spacing: -0.5px;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.indigo-val {
|
||
color: #4f46e5;
|
||
}
|
||
|
||
.emerald-val {
|
||
color: #059669;
|
||
}
|
||
|
||
.amber-val {
|
||
color: #d97706;
|
||
}
|
||
|
||
.rose-val {
|
||
color: #e11d48;
|
||
}
|
||
|
||
.metric-sub-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
font-size: 11.5px;
|
||
font-weight: 600;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
width: fit-content;
|
||
}
|
||
|
||
.metric-sub-pill.info {
|
||
background: #eef2ff;
|
||
color: #4f46e5;
|
||
}
|
||
|
||
.metric-sub-pill.success {
|
||
background: #f0fdf4;
|
||
color: #16a34a;
|
||
}
|
||
|
||
.metric-sub-pill.warning {
|
||
background: #fffbeb;
|
||
color: #d97706;
|
||
}
|
||
|
||
.metric-sub-pill.danger {
|
||
background: #fff1f2;
|
||
color: #e11d48;
|
||
}
|
||
|
||
/* ================= 通用 Section 卡片 ================= */
|
||
.section-card {
|
||
background: #ffffff;
|
||
border-radius: 16px;
|
||
padding: 22px 24px;
|
||
border: 1px solid rgba(226, 232, 240, 0.8);
|
||
box-shadow:
|
||
0 1px 3px rgba(15, 23, 42, 0.04),
|
||
0 4px 12px rgba(15, 23, 42, 0.02);
|
||
}
|
||
|
||
.section-header-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.section-header-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
color: #1e293b;
|
||
}
|
||
|
||
.header-icon {
|
||
color: #3b82f6;
|
||
font-size: 20px;
|
||
}
|
||
|
||
.header-icon.rose-icon {
|
||
color: #f43f5e;
|
||
}
|
||
|
||
.header-badge {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: #64748b;
|
||
background: #f1f5f9;
|
||
padding: 3px 10px;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
/* ================= 上下架 3 组 Mini-Bars ================= */
|
||
.listing-daily-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.listing-stat-card {
|
||
padding: 20px;
|
||
border-radius: 14px;
|
||
background: #f8fafc;
|
||
border: 1px solid #e2e8f0;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.listing-stat-card:hover {
|
||
background: #ffffff;
|
||
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.06);
|
||
border-color: #cbd5e1;
|
||
}
|
||
|
||
.listing-stat-head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.listing-stat-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.tone-publish .listing-stat-icon {
|
||
background: #ecfdf5;
|
||
color: #10b981;
|
||
}
|
||
|
||
.tone-offline .listing-stat-icon {
|
||
background: #fffbeb;
|
||
color: #f59e0b;
|
||
}
|
||
|
||
.tone-trade .listing-stat-icon {
|
||
background: #eff6ff;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.listing-stat-meta span {
|
||
display: block;
|
||
font-size: 12.5px;
|
||
font-weight: 500;
|
||
color: #64748b;
|
||
}
|
||
|
||
.listing-stat-meta strong {
|
||
display: block;
|
||
font-size: 24px;
|
||
font-weight: 800;
|
||
color: #0f172a;
|
||
line-height: 1.1;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.listing-stat-hint {
|
||
margin: 10px 0 16px;
|
||
font-size: 11.5px;
|
||
color: #94a3b8;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.mini-bars {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||
gap: 8px;
|
||
align-items: end;
|
||
min-height: 90px;
|
||
padding-top: 8px;
|
||
border-top: 1px dashed #e2e8f0;
|
||
}
|
||
|
||
.mini-bar-col {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 4px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.mini-bar {
|
||
width: 100%;
|
||
max-width: 20px;
|
||
border-radius: 4px 4px 1px 1px;
|
||
min-height: 6px;
|
||
transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.mini-bar.publish {
|
||
background: linear-gradient(180deg, #34d399, #10b981);
|
||
}
|
||
|
||
.mini-bar.offline {
|
||
background: linear-gradient(180deg, #fbbf24, #f59e0b);
|
||
}
|
||
|
||
.mini-bar.trade {
|
||
background: linear-gradient(180deg, #60a5fa, #3b82f6);
|
||
}
|
||
|
||
.mini-bar-col em {
|
||
font-style: normal;
|
||
font-size: 10px;
|
||
color: #94a3b8;
|
||
}
|
||
|
||
.mini-bar-col b {
|
||
font-size: 11px;
|
||
color: #1e293b;
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* ================= 渠道总览 ================= */
|
||
.channel-summary-title {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #334155;
|
||
margin: 16px 0 10px;
|
||
}
|
||
|
||
.listing-channel-summary {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 12px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.listing-channel-item {
|
||
background: #f8fafc;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 12px;
|
||
padding: 14px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.listing-channel-item:hover {
|
||
background: #ffffff;
|
||
border-color: #cbd5e1;
|
||
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
|
||
}
|
||
|
||
.listing-channel-item-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.channel-name {
|
||
font-size: 13.5px;
|
||
font-weight: 700;
|
||
color: #1e293b;
|
||
}
|
||
|
||
.channel-total {
|
||
font-size: 18px;
|
||
font-weight: 800;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.listing-channel-counts {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.count-tag {
|
||
font-size: 11.5px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.count-tag.publish {
|
||
color: #059669;
|
||
}
|
||
|
||
.count-tag.offline {
|
||
color: #d97706;
|
||
}
|
||
|
||
.count-tag.trade {
|
||
color: #2563eb;
|
||
}
|
||
|
||
.trend-table-wrap {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.listing-channel-block {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.listing-uploader-block {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.listing-uploader-summary {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||
gap: 12px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.listing-uploader-item {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 8px;
|
||
border: 1px solid #dbeafe;
|
||
border-radius: 10px;
|
||
background: #eff6ff;
|
||
padding: 14px;
|
||
}
|
||
|
||
.uploader-name {
|
||
overflow: hidden;
|
||
color: #1e3a8a;
|
||
font-size: 13.5px;
|
||
font-weight: 700;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.uploader-total {
|
||
margin-left: auto;
|
||
color: #2563eb;
|
||
font-size: 20px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.uploader-unit {
|
||
color: #64748b;
|
||
font-size: 11.5px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.channel-badge {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.trend-val {
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||
font-weight: 700;
|
||
font-size: 12.5px;
|
||
}
|
||
|
||
.text-emerald {
|
||
color: #059669;
|
||
}
|
||
.text-amber {
|
||
color: #d97706;
|
||
}
|
||
.text-blue {
|
||
color: #2563eb;
|
||
}
|
||
.text-slate {
|
||
color: #475569;
|
||
}
|
||
|
||
/* ================= 待处理事项 & 快捷入口 ================= */
|
||
.dashboard-panels-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 20px;
|
||
}
|
||
|
||
.panel-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.panel-header-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.panel-header-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: #1e293b;
|
||
}
|
||
|
||
.panel-icon {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.amber-icon {
|
||
color: #f59e0b;
|
||
}
|
||
|
||
.blue-icon {
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.pending-total-badge {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #64748b;
|
||
background: #f1f5f9;
|
||
padding: 3px 10px;
|
||
border-radius: 20px;
|
||
}
|
||
|
||
.pending-total-badge.has-pending {
|
||
color: #e11d48;
|
||
background: #fff1f2;
|
||
}
|
||
|
||
.pending-list-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.pending-item-card {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px 16px;
|
||
background: #f8fafc;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 12px;
|
||
text-decoration: none;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.pending-item-card:hover:not(.static-card) {
|
||
background: #ffffff;
|
||
border-color: #cbd5e1;
|
||
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
|
||
transform: translateX(2px);
|
||
}
|
||
|
||
.pending-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
|
||
.pending-label {
|
||
font-size: 13.5px;
|
||
font-weight: 700;
|
||
color: #1e293b;
|
||
}
|
||
|
||
.pending-desc {
|
||
font-size: 11.5px;
|
||
color: #94a3b8;
|
||
}
|
||
|
||
.pending-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 28px;
|
||
height: 28px;
|
||
padding: 0 8px;
|
||
border-radius: 14px;
|
||
font-size: 13px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.badge-urgent {
|
||
background: #fff1f2;
|
||
color: #e11d48;
|
||
border: 1px solid #fecdd3;
|
||
}
|
||
|
||
.badge-zero {
|
||
background: #f1f5f9;
|
||
color: #94a3b8;
|
||
}
|
||
|
||
/* ================= 快捷入口 ================= */
|
||
.quick-nav-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 12px;
|
||
}
|
||
|
||
.quick-tile {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10px;
|
||
padding: 18px 12px;
|
||
background: #f8fafc;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 14px;
|
||
text-decoration: none;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.quick-tile:hover {
|
||
background: #ffffff;
|
||
border-color: #cbd5e1;
|
||
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.06);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.quick-icon-wrap {
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 20px;
|
||
}
|
||
|
||
.quick-icon-wrap.indigo {
|
||
background: #eef2ff;
|
||
color: #6366f1;
|
||
}
|
||
|
||
.quick-icon-wrap.blue {
|
||
background: #eff6ff;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.quick-icon-wrap.rose {
|
||
background: #fff1f2;
|
||
color: #f43f5e;
|
||
}
|
||
|
||
.quick-icon-wrap.emerald {
|
||
background: #ecfdf5;
|
||
color: #10b981;
|
||
}
|
||
|
||
.quick-icon-wrap.teal {
|
||
background: #f0fdfa;
|
||
color: #0d9488;
|
||
}
|
||
|
||
.quick-icon-wrap.slate {
|
||
background: #f1f5f9;
|
||
color: #64748b;
|
||
}
|
||
|
||
.quick-title {
|
||
font-size: 12.5px;
|
||
font-weight: 700;
|
||
color: #334155;
|
||
}
|
||
|
||
/* ================= 表格容器与通用组件 ================= */
|
||
.table-section-card {
|
||
padding-bottom: 16px;
|
||
}
|
||
|
||
.table-card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.table-header-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: #1e293b;
|
||
}
|
||
|
||
.view-all-link {
|
||
font-size: 12.5px;
|
||
font-weight: 600;
|
||
color: #3b82f6;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.view-all-link:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.table-container {
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
border: 1px solid #e2e8f0;
|
||
}
|
||
|
||
.custom-enterprise-table :deep(.el-table__header-wrapper th) {
|
||
background-color: #f8fafc !important;
|
||
color: #475569;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
padding: 12px 0;
|
||
border-bottom: 1px solid #e2e8f0;
|
||
}
|
||
|
||
.custom-enterprise-table :deep(.el-table__body-wrapper td) {
|
||
padding: 12px 0;
|
||
font-size: 13px;
|
||
color: #334155;
|
||
}
|
||
|
||
.order-link {
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||
font-size: 12.5px;
|
||
font-weight: 600;
|
||
color: #3b82f6;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.order-link:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.mono-code {
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.status-tag {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.amount-val {
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||
font-size: 13.5px;
|
||
font-weight: 700;
|
||
color: #0f172a;
|
||
}
|
||
|
||
.time-cell {
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.dispute-type {
|
||
font-size: 12.5px;
|
||
color: #475569;
|
||
}
|
||
|
||
@media (max-width: 1100px) {
|
||
.listing-daily-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.dashboard-panels-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.quick-nav-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
}
|
||
</style>
|