diff --git a/docs/Phase2-分销看板验收.md b/docs/Phase2-分销看板验收.md new file mode 100644 index 0000000..da1664c --- /dev/null +++ b/docs/Phase2-分销看板验收.md @@ -0,0 +1,81 @@ +# Phase 2 验收报告(分销看板) + +> 日期:2026-10-30 +> 项目:解忧商行(hfb_jysh) +> 范围:后台分销看板 +> 结论:**本切片通过,只读统计页已接真实接口,typecheck/build 通过** + +--- + +## 1. 页面与路由 + +| 页面 | 路由 | 文件 | +| --- | --- | --- | +| 分销看板 | `/console-jy8k3m/distribution/dashboard` | `frontend/src/features/admin/AdminDistributionDashboardPage.tsx` | + +权限:`distribution:site_view`。 + +## 2. 接口 + +| 能力 | 接口 | 权限码 | +| --- | --- | --- | +| 站点经营分析 | `GET /api/admin/distribution/sites/:id/analytics` | `distribution:site_view` | + +后端返回: + +- 今日/累计收号 +- 在售商品数 +- 今日订单数 +- 今日 GMV、今日利润、本月利润 +- 站点可用余额 +- 今日新增用户、累计注册用户、成交客户数 +- 最近 7 天趋势:日期、收号、订单、GMV、利润 + +结果在 Redis 缓存 60 秒,前端刷新会重新请求。 + +## 3. 页面能力 + +- 站点选择器复用 `DistributionSiteSelect`,站点 ID 写入 URL。 +- 10 个 KPI 卡片:今日收号、在售商品、今日订单、今日 GMV、今日利润、本月利润、可用余额、今日新增、成交客户、累计用户。 +- 金额全部按整数分展示,统一使用 `formatMoney(cents)`。 +- 近 7 天趋势: + - 日期、收号、订单、GMV、利润 + - 利润横向条形图,按 7 天最大利润归一化 + - 窄屏横向滚动,不压缩表格 +- 快捷入口:分销订单、分销用户、站点钱包。 +- 无权限时停止请求并提示;站点为空时显示统一空态。 + +## 4. 共用改造 + +- `frontend/src/features/admin/types.ts`:新增分销分析、趋势点类型。 +- `frontend/src/features/admin/api.ts`:新增 `fetchAdminDistributionAnalytics`。 +- `frontend/src/app/AppRouter.tsx`:新增 `distribution/dashboard`。 +- `frontend/src/layouts/AdminLayout.tsx`:分销菜单新增「分销看板」并置于分组首位。 +- `frontend/src/styles/admin-distribution.css`:新增看板网格、趋势列表与利润条样式。 + +## 5. 验证结果 + +```text +cd frontend && npx tsc --noEmit 通过 +cd frontend && npm run build 通过(入口 525.59 kB,gzip 168.56 kB) + +GET /console-jy8k3m/distribution/dashboard 200 +GET /api/admin/distribution/sites/1/analytics 200 +``` + +开发库无分销数据,接口返回全 0,并正常返回最近 7 天日期趋势(2026-10-24 至 2026-10-30)。未调用任何写接口。 + +## 6. 已知限制 + +1. 趋势窗口固定为最近 7 天,后端暂不支持自定义区间。 +2. 统计缓存 60 秒,刚发生的订单/收号可能短暂延迟。 +3. 今日口径按服务器时区自然日计算。 +4. 不提供导出;需要导出时可另接报表能力。 +5. 开发库无真实站点数据,仅验证了零值响应与页面空态,未做真实经营数据视觉回归。 + +## 7. 下一步建议 + +1. 分销账号同步入口。 +2. 主站支付配置管理页(如主后台仍未迁移)。 +3. 真实站点数据下回归看板口径与趋势。 +4. 订单结算比例全面 bps 化与真实订单端到端回归。 diff --git a/docs/项目计划.md b/docs/项目计划.md index b95fc6c..41c1f18 100644 --- a/docs/项目计划.md +++ b/docs/项目计划.md @@ -395,7 +395,7 @@ hfb_jysh/ ### Phase 4:管理后台核心(4–5 周) -> 状态:**进行中(2026-10-30)**。已完成仪表盘、订单、退款、提现、商品、用户、申诉、财务看板、资金出款、运营开支、公告管理、审计日志、系统配置、分销站点、站点钱包、分销订单、结算批次、分销用户、站点公告、分销商品、支付通道、规则版本、二维码池、站点短信与邀请返利;剩余分销看板、账号同步、主站支付配置管理等细分长尾和真实订单端到端回归。 +> 状态:**进行中(2026-10-30)**。已完成仪表盘、订单、退款、提现、商品、用户、申诉、财务看板、资金出款、运营开支、公告管理、审计日志、系统配置、分销站点、分销看板、站点钱包、分销订单、结算批次、分销用户、站点公告、分销商品、支付通道、规则版本、二维码池、站点短信与邀请返利;剩余分销账号同步、主站支付配置管理等细分长尾和真实订单端到端回归。 - 后台登录、工作台、订单列表、订单详情。 - 商品列表、商品详情、审核。 diff --git a/frontend/src/app/AppRouter.tsx b/frontend/src/app/AppRouter.tsx index 15854fc..009c96d 100644 --- a/frontend/src/app/AppRouter.tsx +++ b/frontend/src/app/AppRouter.tsx @@ -185,6 +185,11 @@ const AdminDistributionWalletPage = lazy(() => default: module.AdminDistributionWalletPage, })) ) +const AdminDistributionDashboardPage = lazy(() => + import('@/features/admin/AdminDistributionDashboardPage').then(module => ({ + default: module.AdminDistributionDashboardPage, + })) +) const AdminDistributionSMSPage = lazy(() => import('@/features/admin/AdminDistributionSMSPage').then(module => ({ default: module.AdminDistributionSMSPage, @@ -299,6 +304,10 @@ export function AppRouter() { } /> } /> } /> + } + /> } /> state.admin?.permissions) + const canView = Boolean( + permissions?.includes('*') || permissions?.includes('distribution:site_view') + ) + const selection = useDistributionSiteSelection() + const siteId = selection.siteId + + const analyticsQuery = useQuery({ + queryKey: ['admin-distribution-analytics', siteId], + queryFn: () => fetchAdminDistributionAnalytics(siteId as number), + enabled: canView && Boolean(siteId), + }) + + const data = analyticsQuery.data + const trendDays = data?.trend_days ?? [] + const maxProfit = useMemo( + () => Math.max(1, ...trendDays.map(point => point.profit_cent)), + [trendDays] + ) + + const metrics = [ + { + key: 'today_received', + label: '今日收号', + value: count(data?.today_received_count), + hint: `累计收货 ${count(data?.total_received_count)} 件`, + }, + { + key: 'active', + label: '在售商品', + value: count(data?.active_listing_count), + hint: '已上架且审核通过', + }, + { + key: 'today_orders', + label: '今日订单', + value: count(data?.today_order_count), + hint: '按订单创建时间统计', + }, + { + key: 'today_gmv', + label: '今日 GMV', + value: formatMoney(data?.today_gmv_cent), + hint: '按支付快照实付金额', + }, + { + key: 'today_profit', + label: '今日利润', + value: formatMoney(data?.today_profit_cent), + hint: '站点参与方利润', + accent: true, + }, + { + key: 'month_profit', + label: '本月利润', + value: formatMoney(data?.month_profit_cent), + hint: '自然月累计', + }, + { + key: 'balance', + label: '可用余额', + value: formatMoney(data?.available_balance_cent), + hint: '可申请提现', + }, + { + key: 'today_users', + label: '今日新增', + value: count(data?.today_user_count), + hint: `累计注册 ${count(data?.total_user_count)} 人`, + }, + { + key: 'order_users', + label: '成交客户', + value: count(data?.total_order_user_count), + hint: '已产生有效订单', + }, + { + key: 'total_users', + label: '累计用户', + value: count(data?.total_user_count), + hint: '本站注册用户', + }, + ] + + const adminBase = getAdminBasePath() + const go = (path: string) => navigate(`${adminBase}${path}`) + + return ( +
+ + 近 7 天趋势 + {selection.site ? {selection.site.site_name} : null} + {analyticsQuery.isFetching ? 正在刷新 : null} + + } + extra={ + + + + + + + } + /> + + {!canView ? ( + + ) : ( + + )} + + {analyticsQuery.isError ? ( + void analyticsQuery.refetch()}> + 重试 + + } + /> + ) : null} + +
+ {metrics.map(metric => ( + + {metric.label} + {metric.value} + {metric.hint} + + ))} +
+ + + + 近 7 天经营趋势 + + } + extra={ + + 收号 / 订单 / GMV / 站点利润 + + } + > + {trendDays.length === 0 ? ( + + {analyticsQuery.isPending ? '加载中…' : '暂无趋势数据'} + + ) : ( +
+
+ 日期 + 收号 + 订单 + GMV + 利润 + 利润趋势 +
+ {trendDays.map(point => ( +
+ {point.date.slice(5)} + {count(point.received_count)} + {count(point.order_count)} + {formatMoney(point.gmv_cent)} + {formatMoney(point.profit_cent)} + + + +
+ ))} +
+ )} +
+
+ ) +} diff --git a/frontend/src/features/admin/api.ts b/frontend/src/features/admin/api.ts index a31cb08..aeff7bd 100644 --- a/frontend/src/features/admin/api.ts +++ b/frontend/src/features/admin/api.ts @@ -13,6 +13,7 @@ import type { AdminDispute, AdminDisputePage, AdminDisputeQuery, + AdminDistributionAnalytics, AdminDistributionInvitationConfig, AdminDistributionOrder, AdminDistributionOrderPage, @@ -511,6 +512,13 @@ export async function fetchAdminDistributionWallet(id: number | string) { return data.data } +export async function fetchAdminDistributionAnalytics(id: number | string) { + const { data } = await adminClient.get>( + `/admin/distribution/sites/${id}/analytics` + ) + return data.data +} + export async function applyAdminDistributionWithdrawal( id: number | string, payload: ApplyAdminDistributionWithdrawalPayload diff --git a/frontend/src/features/admin/types.ts b/frontend/src/features/admin/types.ts index 6437e88..e0d030f 100644 --- a/frontend/src/features/admin/types.ts +++ b/frontend/src/features/admin/types.ts @@ -1256,3 +1256,27 @@ export interface AdminPaymentConfigOption { environment: string is_default: boolean } + +/** 分销站点看板:金额单位全部为分,趋势固定为最近 7 天。 */ +export interface AdminDistributionTrendPoint { + date: string + received_count: number + order_count: number + gmv_cent: number + profit_cent: number +} + +export interface AdminDistributionAnalytics { + today_received_count: number + total_received_count: number + active_listing_count: number + today_order_count: number + today_gmv_cent: number + today_profit_cent: number + month_profit_cent: number + available_balance_cent: number + today_user_count: number + total_user_count: number + total_order_user_count: number + trend_days: AdminDistributionTrendPoint[] +} diff --git a/frontend/src/layouts/AdminLayout.tsx b/frontend/src/layouts/AdminLayout.tsx index 79a6847..d0586b5 100644 --- a/frontend/src/layouts/AdminLayout.tsx +++ b/frontend/src/layouts/AdminLayout.tsx @@ -98,6 +98,7 @@ const allNavGroups: NavGroup[] = [ label: '分销', icon: , children: [ + { key: '/distribution/dashboard', label: '分销看板', permission: 'distribution:site_view' }, { key: '/distribution/sites', label: '站点管理', permission: 'distribution:site_view' }, { key: '/distribution/orders', label: '分销订单', permission: 'distribution:order_view' }, { @@ -153,6 +154,7 @@ const sectionMeta: Record = { '/finance/operating-expenses': { title: '运营开支', description: '登记与核对日常运营支出。' }, '/withdrawals': { title: '提现审核', description: '审核提现申请与打款确认。' }, '/distribution/sites': { title: '分销站点', description: '管理分站、规则、结算与站点钱包。' }, + '/distribution/dashboard': { title: '分销看板', description: '查看站点收号、订单、利润与近 7 天趋势。' }, '/distribution/orders': { title: '分销订单', description: '核对分销订单快照与冷却期释放状态。' }, '/distribution/settlements': { title: '结算批次', description: '生成人工批次并核对、释放站内份额。' }, '/distribution/users': { title: '分销用户', description: '查看站点注册客户、下单客户与消费贡献。' }, diff --git a/frontend/src/styles/admin-distribution.css b/frontend/src/styles/admin-distribution.css index 94ad7a0..8cd9410 100644 --- a/frontend/src/styles/admin-distribution.css +++ b/frontend/src/styles/admin-distribution.css @@ -167,7 +167,56 @@ margin-bottom: 14px; } -/* 二维码池 */ +/* 分销看板 */ +.admin-dashboard-grid { + grid-template-columns: repeat(5, minmax(0, 1fr)); +} + +.admin-trend-card { + margin-top: 16px; +} + +.admin-trend-scroll { + overflow-x: auto; +} + +.admin-trend-row { + display: grid; + grid-template-columns: 80px 70px 70px 130px 130px minmax(160px, 1fr); + align-items: center; + gap: 12px; + min-width: 760px; + padding: 10px 0; + border-bottom: 1px dashed var(--line); + font-size: 13px; +} + +.admin-trend-row:last-child { + border-bottom: none; +} + +.admin-trend-row--head { + color: var(--ink-muted); + font-size: 11px; + letter-spacing: 0.04em; +} + +.admin-trend-bar { + display: block; + height: 8px; + overflow: hidden; + border-radius: 999px; + background: #ece7dc; +} + +.admin-trend-bar i { + display: block; + height: 100%; + border-radius: inherit; + background: var(--brand-seal); +} + +/* 分销用户 */ .admin-qr-thumb { border: 1px solid var(--line); border-radius: 3px;