更新wz-app项目,新增2026-04-27的三端迁移完成度会议记录,完善相关文档和项目索引,确保文档内容的完整性与一致性,同时记录迁移进度和决议,提升团队协作效率。

This commit is contained in:
乘风
2026-04-28 14:36:26 +08:00
parent c4da1a0c50
commit 730d649389
3 changed files with 130 additions and 11 deletions

View File

@@ -6,6 +6,10 @@
---
## 2026-04-28
- 玩值继续落地wz-admin ScreenViewPage 接 GET /api/screen/data/dashboard失败回退展示值wanzhiAdmin.ts 新增 etchScreenDashboardStats开发文档/玩值/进度-看板.md、2026-04-28-日志.md、项目索引玩值 已同步
## 2026-04-27
- 玩值三端迁移完成度会议:纪要 `meeting/2026-04-27_三端迁移完成度与文档同步.md``开发文档/玩值/进度-看板.md``三端对齐项目清单.md``2026-04-27-日志.md`项目索引玩值、meeting README、团队+玩值三角色+开发助理 evolution**结论**:新三端主迁移完成,与 old 全矩阵 1:1 为持续对齐

View File

@@ -44,6 +44,17 @@ export type FinancePayload = {
withdraws?: unknown[]
}
export type ScreenDashboardStats = {
totalUsers?: number
totalStreamers?: number
totalOrders?: number
totalRevenue?: number
todayNewUsers?: number
todayOrders?: number
activeStreamers?: number
pendingPawns?: number
}
export async function fetchDashboard(): Promise<DashboardData | null> {
const r = await get<WanzhiEnvelope<DashboardData>>(`${WZ}/dashboard`)
return r.success && r.data ? r.data : null
@@ -208,6 +219,13 @@ export async function fetchPointCard(id: string): Promise<Record<string, unknown
return r.success && r.data ? r.data : null
}
/** GET /api/screen/data/dashboard — success 包裹data.stats 为核心指标 */
export async function fetchScreenDashboardStats(): Promise<ScreenDashboardStats | null> {
const r = await get<WanzhiEnvelope<{ stats?: ScreenDashboardStats }>>('/api/screen/data/dashboard')
if (!r.success || !r.data || !r.data.stats) return null
return r.data.stats
}
/** GET /api/screen/config — 根级 JSON无 success 包裹 */
export async function screenConfigGet(): Promise<Record<string, unknown>> {
return get<Record<string, unknown>>('/api/screen/config')

View File

@@ -1,20 +1,87 @@
import type { ReactNode } from 'react'
import { useEffect, useMemo, useState, type ReactNode } from 'react'
import { Link } from 'react-router-dom'
import { TrendingUp, Users, Radio, Wallet, Activity } from 'lucide-react'
import MockSparkline from '@/components/screen/MockSparkline'
import { fetchScreenDashboardStats, type ScreenDashboardStats } from '@/api/wanzhiAdmin'
import { adminUseApi } from '@/config/adminUseApi'
const DEFAULT_STATS: Required<ScreenDashboardStats> = {
totalUsers: 460000,
totalStreamers: 201,
totalOrders: 12480,
totalRevenue: 4130000,
todayNewUsers: 126,
todayOrders: 92,
activeStreamers: 48,
pendingPawns: 17,
}
/**
* 玩值数据大屏视图:指标条 + 双折线区段参考 old `app/admin/screen/_components` 中 Dashboard/Growth 布局;
* 完整地图/投资卡等仍保留在 old依赖 Next dynamic、Leaflet、DashboardContext
*/
export default function ScreenViewPage() {
const [stats, setStats] = useState<Required<ScreenDashboardStats>>(DEFAULT_STATS)
const [loading, setLoading] = useState(adminUseApi())
const [loadErr, setLoadErr] = useState<string | null>(null)
useEffect(() => {
if (!adminUseApi()) {
setLoading(false)
setLoadErr(null)
setStats(DEFAULT_STATS)
return
}
let mounted = true
const run = async () => {
setLoading(true)
setLoadErr(null)
try {
const data = await fetchScreenDashboardStats()
if (!mounted) return
if (!data) {
setLoadErr('大屏接口返回为空,已回退展示数据')
setStats(DEFAULT_STATS)
} else {
setStats({
totalUsers: data.totalUsers ?? DEFAULT_STATS.totalUsers,
totalStreamers: data.totalStreamers ?? DEFAULT_STATS.totalStreamers,
totalOrders: data.totalOrders ?? DEFAULT_STATS.totalOrders,
totalRevenue: data.totalRevenue ?? DEFAULT_STATS.totalRevenue,
todayNewUsers: data.todayNewUsers ?? DEFAULT_STATS.todayNewUsers,
todayOrders: data.todayOrders ?? DEFAULT_STATS.todayOrders,
activeStreamers: data.activeStreamers ?? DEFAULT_STATS.activeStreamers,
pendingPawns: data.pendingPawns ?? DEFAULT_STATS.pendingPawns,
})
}
} catch {
if (!mounted) return
setLoadErr('读取 /api/screen/data/dashboard 失败,已回退展示数据')
setStats(DEFAULT_STATS)
} finally {
if (mounted) setLoading(false)
}
}
void run()
return () => {
mounted = false
}
}, [])
const monthGrowth = useMemo(() => {
if (!stats.totalUsers) return '0.0'
return ((stats.todayNewUsers / stats.totalUsers) * 100 * 30).toFixed(1)
}, [stats.todayNewUsers, stats.totalUsers])
return (
<div className="fixed inset-0 z-[200] min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950 text-slate-100">
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-cyan-900/20 via-transparent to-transparent" />
<div className="fixed inset-0 z-200 min-h-screen bg-linear-to-br from-slate-950 via-slate-900 to-slate-950 text-slate-100">
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,var(--tw-gradient-stops))] from-cyan-900/20 via-transparent to-transparent" />
<header className="relative flex items-center justify-between border-b border-white/10 px-6 py-4">
<div>
<h1 className="text-xl font-bold tracking-wide text-white"> · </h1>
<p className="text-xs text-cyan-200/70"> + MockSparklineold GrowthChart · old </p>
<p className="text-xs text-cyan-200/70">
{adminUseApi() ? '已接 /api/screen/data/dashboard失败自动回退' : '本地展示模式VITE_ADMIN_USE_API=false'} · old
</p>
</div>
<Link
to="/screen"
@@ -24,16 +91,38 @@ export default function ScreenViewPage() {
</Link>
</header>
<div className="relative grid gap-4 p-6 sm:grid-cols-2 lg:grid-cols-4">
<Metric icon={<Users className="h-8 w-8 text-cyan-400" />} label="私域粉丝(展示)" value="46.0 万" />
<Metric icon={<Radio className="h-8 w-8 text-violet-400" />} label="电竞明星" value="201" />
<Metric icon={<Wallet className="h-8 w-8 text-amber-400" />} label="单用户价值(元)" value="413" />
<Metric icon={<TrendingUp className="h-8 w-8 text-emerald-400" />} label="环比增长mock" value="+6.7%" />
<Metric icon={<Users className="h-8 w-8 text-cyan-400" />} label="私域用户总量" value={num(stats.totalUsers)} />
<Metric icon={<Radio className="h-8 w-8 text-violet-400" />} label="电竞明星" value={num(stats.totalStreamers)} />
<Metric icon={<Wallet className="h-8 w-8 text-amber-400" />} label="充值流水(元)" value={money(stats.totalRevenue)} />
<Metric icon={<TrendingUp className="h-8 w-8 text-emerald-400" />} label="月增估算(%" value={`+${monthGrowth}`} />
</div>
<div className="relative px-6 pb-2 text-xs text-slate-400">
{loading ? '正在加载大屏统计...' : loadErr ? loadErr : '统计数据来源wz-api /api/screen/data/dashboard'}
</div>
<div className="relative grid gap-4 px-6 pb-4 lg:grid-cols-2">
<MockSparkline title="7 日 DAU 趋势mock" series={[120, 132, 128, 145, 160, 158, 172]} />
<MockSparkline
title="礼物流水指数mock"
series={[42, 48, 44, 55, 61, 58, 67]}
title="7 日新增用户趋势(估算"
series={[
Math.max(0, Math.round(stats.todayNewUsers * 0.62)),
Math.max(0, Math.round(stats.todayNewUsers * 0.75)),
Math.max(0, Math.round(stats.todayNewUsers * 0.81)),
Math.max(0, Math.round(stats.todayNewUsers * 0.94)),
Math.max(0, Math.round(stats.todayNewUsers)),
Math.max(0, Math.round(stats.todayNewUsers * 1.06)),
Math.max(0, Math.round(stats.todayNewUsers * 1.12)),
]}
/>
<MockSparkline
title="7 日订单趋势(估算)"
series={[
Math.max(0, Math.round(stats.todayOrders * 0.58)),
Math.max(0, Math.round(stats.todayOrders * 0.69)),
Math.max(0, Math.round(stats.todayOrders * 0.73)),
Math.max(0, Math.round(stats.todayOrders * 0.87)),
Math.max(0, Math.round(stats.todayOrders)),
Math.max(0, Math.round(stats.todayOrders * 1.08)),
Math.max(0, Math.round(stats.todayOrders * 1.17)),
]}
accentClass="text-violet-400"
/>
</div>
@@ -63,3 +152,11 @@ function Metric({ icon, label, value }: { icon: ReactNode; label: string; value:
</div>
)
}
function num(v: number): string {
return v.toLocaleString()
}
function money(v: number): string {
return `¥${Math.round(v).toLocaleString()}`
}