2026-05-11 13:07:40 +08:00
|
|
|
|
import { Button, CapsuleTabs, DotLoading, ErrorBlock, Tag, Toast } from 'antd-mobile'
|
|
|
|
|
|
import { useMemo, useState } from 'react'
|
|
|
|
|
|
import { MiniTrendChart } from '../../../components/charts/MiniTrendChart'
|
|
|
|
|
|
import { KpiCard } from '../../../components/kpi/KpiCard'
|
|
|
|
|
|
import { formatMoney, formatNumber } from '../../../utils/format'
|
2026-05-11 13:21:35 +08:00
|
|
|
|
import { useDashboardOverview } from '../api'
|
|
|
|
|
|
import { buildDailyReportArchiveHtml } from '../archive'
|
2026-05-11 13:07:40 +08:00
|
|
|
|
import type { DashboardOverview, RiskLevel, SnapshotSlot, TodaySnapshot } from '../types'
|
|
|
|
|
|
|
|
|
|
|
|
const snapshotStatusMeta = {
|
|
|
|
|
|
pending: { color: 'default', label: '待生成' },
|
|
|
|
|
|
success: { color: 'success', label: '已生成' },
|
|
|
|
|
|
failed: { color: 'danger', label: '失败' },
|
|
|
|
|
|
temporary: { color: 'warning', label: '临时' },
|
|
|
|
|
|
} as const
|
|
|
|
|
|
|
|
|
|
|
|
const riskLevelMeta: Record<RiskLevel, { color: 'danger' | 'warning' | 'default'; label: string }> = {
|
|
|
|
|
|
red: { color: 'danger', label: '红色' },
|
|
|
|
|
|
yellow: { color: 'warning', label: '黄色' },
|
|
|
|
|
|
gray: { color: 'default', label: '灰色' },
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const snapshotSlotMeta: Record<
|
|
|
|
|
|
SnapshotSlot,
|
|
|
|
|
|
{
|
|
|
|
|
|
title: string
|
|
|
|
|
|
subtitle: string
|
|
|
|
|
|
metricLabels: {
|
|
|
|
|
|
primaryUsers: string
|
|
|
|
|
|
primaryOrders: string
|
|
|
|
|
|
amount: string
|
|
|
|
|
|
paidAmount: string
|
|
|
|
|
|
merchandise: string
|
|
|
|
|
|
bonus: string
|
|
|
|
|
|
}
|
|
|
|
|
|
checklist: string[]
|
|
|
|
|
|
}
|
|
|
|
|
|
> = {
|
|
|
|
|
|
'1015': {
|
|
|
|
|
|
title: '上午抢购快报',
|
|
|
|
|
|
subtitle: '用户集中抢购上一天用户寄卖的商品,重点看成交、付款和采购用户是否达标。',
|
|
|
|
|
|
metricLabels: {
|
|
|
|
|
|
primaryUsers: '抢购用户',
|
|
|
|
|
|
primaryOrders: '抢购订单',
|
|
|
|
|
|
amount: '抢购成交额',
|
|
|
|
|
|
paidAmount: '已支付金额',
|
|
|
|
|
|
merchandise: '成交商品',
|
|
|
|
|
|
bonus: '相关奖金',
|
|
|
|
|
|
},
|
|
|
|
|
|
checklist: ['抢购成交额是否低于昨日同节点', '采购用户是否异常回落', '付款金额与成交额是否明显偏离', '高货值寄卖商品是否完成消化'],
|
|
|
|
|
|
},
|
|
|
|
|
|
'1455': {
|
|
|
|
|
|
title: '下午寄卖/转卖快报',
|
|
|
|
|
|
subtitle: '用户把上午抢到的商品继续寄卖或转卖,重点看新增寄售供给和奖金变化是否正常。',
|
|
|
|
|
|
metricLabels: {
|
|
|
|
|
|
primaryUsers: '寄卖用户',
|
|
|
|
|
|
primaryOrders: '转卖订单',
|
|
|
|
|
|
amount: '转卖成交额',
|
|
|
|
|
|
paidAmount: '回款金额',
|
|
|
|
|
|
merchandise: '新增寄售',
|
|
|
|
|
|
bonus: '奖金变化',
|
|
|
|
|
|
},
|
|
|
|
|
|
checklist: ['抢购商品是否按预期转入寄卖', '新增寄售商品是否满足下午供给', '个人奖金与推广奖金是否同步变化', '转卖回款是否出现异常延迟'],
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function QueryState({
|
|
|
|
|
|
isLoading,
|
|
|
|
|
|
isError,
|
|
|
|
|
|
refetch,
|
|
|
|
|
|
title,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
isLoading: boolean
|
|
|
|
|
|
isError: boolean
|
|
|
|
|
|
refetch: () => void
|
|
|
|
|
|
title: string
|
|
|
|
|
|
}) {
|
|
|
|
|
|
if (isLoading) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<section className="loading-page">
|
|
|
|
|
|
<DotLoading color="primary" />
|
|
|
|
|
|
<p>正在加载{title}...</p>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isError) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<section className="error-page">
|
|
|
|
|
|
<ErrorBlock status="default" title={`${title}加载失败`} description="后端接口暂不可用,请确认服务、登录态或接口权限后重试。" />
|
|
|
|
|
|
<Button color="primary" onClick={refetch}>
|
|
|
|
|
|
重新加载
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function OperationsHeader({
|
|
|
|
|
|
kicker,
|
|
|
|
|
|
title,
|
|
|
|
|
|
description,
|
|
|
|
|
|
extra,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
kicker: string
|
|
|
|
|
|
title: string
|
|
|
|
|
|
description: string
|
|
|
|
|
|
extra?: string
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<header className="operations-header">
|
|
|
|
|
|
<p className="eyebrow">{kicker}</p>
|
|
|
|
|
|
<h1>{title}</h1>
|
|
|
|
|
|
<p>{description}</p>
|
|
|
|
|
|
{extra && <span>{extra}</span>}
|
|
|
|
|
|
</header>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function SnapshotDetailCard({ snapshot }: { snapshot: TodaySnapshot }) {
|
|
|
|
|
|
const status = snapshotStatusMeta[snapshot.status]
|
|
|
|
|
|
const slotMeta = snapshotSlotMeta[snapshot.slot]
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<article className="snapshot-detail-card">
|
|
|
|
|
|
<div className="section-title-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="section-kicker">{snapshot.slot}</p>
|
|
|
|
|
|
<h2>{slotMeta.title}</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Tag color={status.color}>{status.label}</Tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p className="snapshot-detail-subtitle">{slotMeta.subtitle}</p>
|
|
|
|
|
|
<p className="snapshot-detail-message">{snapshot.message}</p>
|
|
|
|
|
|
{snapshot.generatedAt && <p className="snapshot-time">生成时间:{snapshot.generatedAt}</p>}
|
|
|
|
|
|
<div className="snapshot-grid snapshot-grid--wide">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
{slotMeta.metricLabels.primaryUsers}
|
|
|
|
|
|
<strong>{formatNumber(snapshot.purchaseUsers)}人</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
{slotMeta.metricLabels.primaryOrders}
|
|
|
|
|
|
<strong>{formatNumber(snapshot.orderCount)}单</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
{slotMeta.metricLabels.amount}
|
|
|
|
|
|
<strong>{formatMoney(snapshot.dealAmount)}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
{slotMeta.metricLabels.paidAmount}
|
|
|
|
|
|
<strong>{formatMoney(snapshot.paidAmount)}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
{slotMeta.metricLabels.merchandise}
|
|
|
|
|
|
<strong>{formatNumber(snapshot.newMerchandiseCount)}件</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
{slotMeta.metricLabels.bonus}
|
|
|
|
|
|
<strong>{formatMoney(Number(snapshot.selfBonusChange) + Number(snapshot.shareBonusChange))}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildDailyReports(data: DashboardOverview) {
|
|
|
|
|
|
return data.trends
|
|
|
|
|
|
.slice(-4)
|
|
|
|
|
|
.reverse()
|
|
|
|
|
|
.map((trend, index) => ({
|
|
|
|
|
|
...trend,
|
|
|
|
|
|
status: index === 0 ? '已生成' : '历史快照',
|
|
|
|
|
|
bonusRate: Number(trend.amount) > 0 ? (Number(trend.bonus) / Number(trend.amount)) * 100 : 0,
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function DailyReportPage() {
|
|
|
|
|
|
const { data, isLoading, isError, refetch } = useDashboardOverview()
|
|
|
|
|
|
const [isArchiving, setIsArchiving] = useState(false)
|
|
|
|
|
|
const state = <QueryState isLoading={isLoading} isError={isError || !data} refetch={() => void refetch()} title="经营日报" />
|
|
|
|
|
|
|
|
|
|
|
|
if (!data) return state
|
|
|
|
|
|
|
|
|
|
|
|
const reports = buildDailyReports(data)
|
|
|
|
|
|
|
|
|
|
|
|
const handleArchive = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setIsArchiving(true)
|
2026-05-11 13:21:35 +08:00
|
|
|
|
const html = buildDailyReportArchiveHtml(data)
|
|
|
|
|
|
const blob = new Blob([html], { type: 'text/html;charset=utf-8' })
|
2026-05-11 13:07:40 +08:00
|
|
|
|
const url = URL.createObjectURL(blob)
|
|
|
|
|
|
const link = document.createElement('a')
|
|
|
|
|
|
link.href = url
|
|
|
|
|
|
link.download = `dashboard-daily-report-${data.businessDate}.html`
|
|
|
|
|
|
document.body.appendChild(link)
|
|
|
|
|
|
link.click()
|
|
|
|
|
|
link.remove()
|
|
|
|
|
|
URL.revokeObjectURL(url)
|
|
|
|
|
|
Toast.show({ icon: 'success', content: '归档 HTML 已生成' })
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
Toast.show({ icon: 'fail', content: '归档生成失败,请稍后重试' })
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsArchiving(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<section className="operations-page">
|
|
|
|
|
|
<OperationsHeader
|
|
|
|
|
|
kicker="Daily Report"
|
|
|
|
|
|
title="经营日报"
|
|
|
|
|
|
description="按日沉淀成交、订单、用户与奖金变化,方便老板回看最近经营节奏。"
|
|
|
|
|
|
extra={`最新数据:${data.businessDate}`}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="section-block compact-section">
|
|
|
|
|
|
<div className="section-title-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="section-kicker">Workday</p>
|
|
|
|
|
|
<h2>上个工作日重点</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Tag color="success">已归档</Tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="kpi-grid kpi-grid--compact">
|
|
|
|
|
|
{data.kpis.slice(0, 4).map((metric) => (
|
|
|
|
|
|
<KpiCard key={metric.key} metric={metric} />
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="section-block">
|
|
|
|
|
|
<div className="section-title-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="section-kicker">Trend</p>
|
|
|
|
|
|
<h2>最近 7 天趋势</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<MiniTrendChart data={data.trends} />
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="section-block">
|
|
|
|
|
|
<div className="section-title-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="section-kicker">Archive</p>
|
|
|
|
|
|
<h2>日报归档</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button className="text-button" type="button" disabled={isArchiving} onClick={() => void handleArchive()}>
|
|
|
|
|
|
{isArchiving ? '生成中...' : '生成归档'}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="report-list">
|
|
|
|
|
|
{reports.map((report) => (
|
|
|
|
|
|
<button className="report-item" key={report.date} type="button">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<strong>{report.date}</strong>
|
|
|
|
|
|
<small>{report.status}</small>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<strong>{formatMoney(report.amount)}</strong>
|
|
|
|
|
|
<small>
|
|
|
|
|
|
{formatNumber(report.orders)} 单 / 奖金占比 {formatNumber(report.bonusRate, 1)}%
|
|
|
|
|
|
</small>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function TodaySnapshotPage() {
|
|
|
|
|
|
const { data, isLoading, isError, refetch } = useDashboardOverview()
|
|
|
|
|
|
const state = <QueryState isLoading={isLoading} isError={isError || !data} refetch={() => void refetch()} title="今日快报" />
|
|
|
|
|
|
|
|
|
|
|
|
if (!data) return state
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<section className="operations-page">
|
|
|
|
|
|
<OperationsHeader
|
|
|
|
|
|
kicker="Today Snapshot"
|
|
|
|
|
|
title="今日快报"
|
|
|
|
|
|
description="10:15 看上一日寄卖商品的抢购结果,14:55 看抢到商品的寄卖/转卖承接情况。"
|
|
|
|
|
|
extra="节点状态随 Mock 场景切换"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="section-block snapshot-page-section">
|
|
|
|
|
|
<div className="section-title-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="section-kicker">Timeline</p>
|
|
|
|
|
|
<h2>节点快报</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="snapshot-stack">
|
|
|
|
|
|
{data.snapshots.map((snapshot) => (
|
|
|
|
|
|
<SnapshotDetailCard key={snapshot.slot} snapshot={snapshot} />
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="section-block">
|
|
|
|
|
|
<div className="section-title-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="section-kicker">Checklist</p>
|
|
|
|
|
|
<h2>节点检查项</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="check-list">
|
|
|
|
|
|
{data.snapshots.flatMap((snapshot) =>
|
|
|
|
|
|
snapshotSlotMeta[snapshot.slot].checklist.map((item) => (
|
|
|
|
|
|
<span key={`${snapshot.slot}-${item}`}>
|
|
|
|
|
|
<strong>{snapshot.slot === '1015' ? '上午' : '下午'}</strong>
|
|
|
|
|
|
{item}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)),
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function RiskCenterPage() {
|
|
|
|
|
|
const { data, isLoading, isError, refetch } = useDashboardOverview()
|
|
|
|
|
|
const [activeLevel, setActiveLevel] = useState<RiskLevel | 'all'>('all')
|
|
|
|
|
|
const state = <QueryState isLoading={isLoading} isError={isError || !data} refetch={() => void refetch()} title="风险中心" />
|
|
|
|
|
|
|
|
|
|
|
|
const filteredRisks = useMemo(() => {
|
|
|
|
|
|
if (!data) return []
|
|
|
|
|
|
if (activeLevel === 'all') return data.risks
|
|
|
|
|
|
return data.risks.filter((risk) => risk.level === activeLevel)
|
|
|
|
|
|
}, [activeLevel, data])
|
|
|
|
|
|
|
|
|
|
|
|
if (!data) return state
|
|
|
|
|
|
|
|
|
|
|
|
const dangerousFunds = data.fundPool.filter((metric) => metric.status === 'warning' || metric.status === 'danger')
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<section className="operations-page">
|
|
|
|
|
|
<OperationsHeader
|
|
|
|
|
|
kicker="Risk Center"
|
|
|
|
|
|
title="风险中心"
|
|
|
|
|
|
description="把资金、积分与数据一致性风险集中处理,优先看红色和黄色事项。"
|
|
|
|
|
|
extra={`${data.risks.length} 条待关注`}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="risk-summary-grid" aria-label="风险概览">
|
|
|
|
|
|
{(['red', 'yellow', 'gray'] as const).map((level) => {
|
|
|
|
|
|
const meta = riskLevelMeta[level]
|
|
|
|
|
|
const count = data.risks.filter((risk) => risk.level === level).length
|
|
|
|
|
|
return (
|
|
|
|
|
|
<button className={`risk-summary-card risk-summary-card--${level}`} key={level} type="button" onClick={() => setActiveLevel(level)}>
|
|
|
|
|
|
<Tag color={meta.color}>{meta.label}</Tag>
|
|
|
|
|
|
<strong>{count}</strong>
|
|
|
|
|
|
<span>条风险</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="section-block">
|
|
|
|
|
|
<CapsuleTabs activeKey={activeLevel} onChange={(key) => setActiveLevel(key as RiskLevel | 'all')}>
|
|
|
|
|
|
<CapsuleTabs.Tab title="全部" key="all" />
|
|
|
|
|
|
<CapsuleTabs.Tab title="红色" key="red" />
|
|
|
|
|
|
<CapsuleTabs.Tab title="黄色" key="yellow" />
|
|
|
|
|
|
<CapsuleTabs.Tab title="灰色" key="gray" />
|
|
|
|
|
|
</CapsuleTabs>
|
|
|
|
|
|
<div className="risk-list">
|
|
|
|
|
|
{filteredRisks.map((risk) => {
|
|
|
|
|
|
const meta = riskLevelMeta[risk.level]
|
|
|
|
|
|
return (
|
|
|
|
|
|
<button className="risk-item" key={risk.id} type="button">
|
|
|
|
|
|
<div className="risk-header">
|
|
|
|
|
|
<Tag color={meta.color}>{meta.label}</Tag>
|
|
|
|
|
|
<span>{risk.type}</span>
|
|
|
|
|
|
<time>{risk.discoveredAt}</time>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<strong>{risk.title}</strong>
|
|
|
|
|
|
<p>{risk.description}</p>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="section-block compact-section">
|
|
|
|
|
|
<div className="section-title-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="section-kicker">Fund Watch</p>
|
|
|
|
|
|
<h2>资金池关注项</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="kpi-grid kpi-grid--compact">
|
|
|
|
|
|
{dangerousFunds.map((metric) => (
|
|
|
|
|
|
<KpiCard key={metric.key} metric={metric} />
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function ProfilePage() {
|
|
|
|
|
|
const { data, isLoading, isError, refetch } = useDashboardOverview()
|
|
|
|
|
|
const state = <QueryState isLoading={isLoading} isError={isError || !data} refetch={() => void refetch()} title="我的" />
|
|
|
|
|
|
|
|
|
|
|
|
if (!data) return state
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<section className="operations-page">
|
|
|
|
|
|
<OperationsHeader
|
|
|
|
|
|
kicker="Profile"
|
|
|
|
|
|
title="我的"
|
|
|
|
|
|
description="展示当前驾驶舱权限、数据环境与演示版本,方便联调时确认口径。"
|
|
|
|
|
|
extra="老板驾驶舱 H5"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="profile-card">
|
|
|
|
|
|
<div className="profile-avatar" aria-hidden="true">
|
|
|
|
|
|
老
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2>老板视角</h2>
|
|
|
|
|
|
<p>可查看经营概览、今日快报、排行与风险预警。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="section-block">
|
|
|
|
|
|
<div className="section-title-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="section-kicker">Environment</p>
|
|
|
|
|
|
<h2>数据环境</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Tag color="warning">Mock</Tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="info-list">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<small>数据日期</small>
|
|
|
|
|
|
<strong>{data.businessDate}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<small>生成时间</small>
|
|
|
|
|
|
<strong>{data.generatedAt}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<small>API 模式</small>
|
|
|
|
|
|
<strong>{import.meta.env.VITE_MOCK_ENABLED === 'false' ? '真实接口' : 'Mock 演示'}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="section-block">
|
|
|
|
|
|
<div className="section-title-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="section-kicker">Permissions</p>
|
|
|
|
|
|
<h2>权限模块</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="check-list">
|
|
|
|
|
|
<span>经营概览:可见</span>
|
|
|
|
|
|
<span>资金池摘要:可见</span>
|
|
|
|
|
|
<span>风险预警:可见</span>
|
|
|
|
|
|
<span>导出能力:待接入</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|