feat(admin): 运营数据 - 增加余额消耗统计、通知排序与用户积分汇总

This commit is contained in:
ddcat666
2026-10-10 07:16:22 +08:00
parent 0cba2ae6b5
commit 18d47d7852
14 changed files with 84 additions and 27 deletions

View File

@@ -1,4 +1,4 @@
import { App, Button, DatePicker, Form, Input, Modal, Popconfirm, Switch } from "antd";
import { App, Button, DatePicker, Form, Input, InputNumber, Modal, Popconfirm, Switch } from "antd";
import type { ColumnsType } from "antd/es/table";
import { Trash2, Edit3, Search } from "lucide-react";
import dayjs, { type Dayjs } from "dayjs";
@@ -29,6 +29,7 @@ type FormValues = {
noticeType: BannerNoticeType;
link?: string;
status: boolean;
sortOrder?: number;
dateRange?: [Dayjs | null, Dayjs | null] | null;
};
@@ -90,6 +91,7 @@ export default function AdminBannerAnnouncementsPanel({
form.setFieldsValue({
noticeType: BANNER_NOTICE_DEFAULT_TYPE,
status: true,
sortOrder: 0,
});
setDialog({ mode: "create" });
}, [form]);
@@ -112,6 +114,7 @@ export default function AdminBannerAnnouncementsPanel({
noticeType: type,
link: banner.link || undefined,
status: banner.status === "active",
sortOrder: banner.sortOrder ?? 0,
dateRange: banner.startsAt || banner.endsAt ? [banner.startsAt ? dayjs(banner.startsAt) : null, banner.endsAt ? dayjs(banner.endsAt) : null] : null,
});
setDialog({ mode: "edit", banner });
@@ -187,6 +190,7 @@ export default function AdminBannerAnnouncementsPanel({
noticeType: selectedNoticeType,
link: values.link?.trim() || undefined,
status: (values.status ? "active" : "disabled") as "active" | "disabled",
sortOrder: values.sortOrder || 0,
startsAt,
endsAt,
};
@@ -428,6 +432,9 @@ export default function AdminBannerAnnouncementsPanel({
<Form.Item name="status" label="发布状态" valuePropName="checked">
<Switch checkedChildren="启用" unCheckedChildren="禁用" />
</Form.Item>
<Form.Item name="sortOrder" label="展示顺序" extra="数字越小越靠前;相同顺序按创建时间倒序">
<InputNumber min={0} max={999999} precision={0} className="w-full" />
</Form.Item>
<Form.Item name="dateRange" label="有效期(可选)" extra="留空表示长期有效;设置时间段后仅在有效期内于前台展示">
<DatePicker.RangePicker showTime format="YYYY-MM-DD HH:mm" className="w-full" />

View File

@@ -122,6 +122,8 @@ export function AdminUserDetailDrawer({ userId, onClose, previousUserId, nextUse
{ key: "role", label: "角色", children: detail.user.role === "admin" ? "管理员" : "普通用户" },
{ key: "status", label: "状态", children: <AdminStatusBadge label={detail.user.status === "active" ? "启用" : "停用"} tone={detail.user.status === "active" ? "success" : "neutral"} /> },
{ key: "available", label: "可用积分", children: formatCredits(detail.account.availableMicrocredits) },
{ key: "recharge", label: "累计充值积分", children: formatCredits(detail.counts.rechargeMicrocredits) },
{ key: "checkin", label: "累计签到积分", children: formatCredits(detail.counts.checkinMicrocredits) },
{ key: "reserved", label: "冻结积分", children: formatCredits(detail.account.reservedMicrocredits) },
{ key: "created", label: "注册时间", children: formatTime(detail.user.createdAt) },
{ key: "login", label: "最后登录", children: formatTime(detail.user.lastLoginAt) },

View File

@@ -300,6 +300,8 @@ export default function AnalyticsPanel({ users, channels }: Props) {
trend={formatCountDelta(currentTrend?.activeUsers, previousTrend?.activeUsers)}
detail={data ? `日 ${formatNumber(data.kpi.dau)} · 周 ${formatNumber(data.kpi.wau)} · 月 ${formatNumber(data.kpi.mau)}` : undefined}
/>
<AnalyticsHealthCard icon={<CircleDollarSign className="size-4" />} label="用户总余额" value={data ? formatCredits(data.kpi.totalBalanceMicrocredits ?? 0) : "--"} detail="当前所有用户可用积分余额" />
<AnalyticsHealthCard icon={<CircleDollarSign className="size-4" />} label="范围消耗" value={data ? formatCredits(data.kpi.totalConsumedMicrocredits ?? 0) : "--"} detail="筛选范围内已计费调用" />
<AnalyticsHealthCard
icon={<Workflow className="size-4" />}
label="生成任务"

View File

@@ -87,6 +87,7 @@ export type BannerAnnouncement = {
noticeType?: BannerNoticeType;
link?: string;
status: "active" | "disabled";
sortOrder?: number;
startsAt?: string;
endsAt?: string;
createdBy: string;
@@ -107,6 +108,7 @@ export type AdminBannerPayload = {
noticeType?: BannerNoticeType;
link?: string;
status: "active" | "disabled";
sortOrder?: number;
startsAt?: string;
endsAt?: string;
};

View File

@@ -126,7 +126,7 @@ export type AdminAuditEvent = {
export type AdminUserDetail = {
user: LocalUser;
account: { userId: string; availableMicrocredits: number; reservedMicrocredits: number; version: number };
counts: { ledgerEntries: number; tasks: number; apiCalls: number; auditEvents: number };
counts: { ledgerEntries: number; tasks: number; apiCalls: number; auditEvents: number; rechargeMicrocredits: number; checkinMicrocredits: number };
storageUsage: {
assetCount: number;
assetBytes: number;
@@ -188,9 +188,11 @@ export type AdminAnalytics = {
successRate: number;
p95DurationMs: number;
currentQueuedTasks: number;
totalBalanceMicrocredits?: number;
totalConsumedMicrocredits?: number;
finance?: AnalyticsFinance | null;
};
trend: Array<{ day: string; tasks: number; requests: number; activeUsers: number; requestSuccessRate: number }>;
trend: Array<{ day: string; tasks: number; requests: number; activeUsers: number; requestSuccessRate: number; consumedMicrocredits?: number }>;
models: Array<{
model: string;
capability: string;