refactor(*): 稳定期治理 - 收敛核心架构、错误语义并修复跨项目数据竞态

This commit is contained in:
ddCat
2026-09-09 22:02:10 +08:00
parent bacd502f5c
commit b5f3413ded
298 changed files with 12681 additions and 26428 deletions

View File

@@ -121,7 +121,7 @@ export default function AdminAnnouncementsPanel({
keyword: queryKeyword || undefined,
status: queryStatus === "all" ? undefined : queryStatus,
page: targetPage,
limit: targetPageSize,
pageSize: targetPageSize,
}),
targetPage,
targetPageSize,
@@ -1024,13 +1024,13 @@ function assertAnnouncementMutationResult(
}
}
function assertAnnouncementListResult(result: unknown, expectedPage: number, expectedLimit: number) {
if (!isRecord(result) || !Array.isArray(result.announcements) || !Number.isInteger(result.total) || (result.total as number) < 0 || result.page !== expectedPage || result.limit !== expectedLimit) {
function assertAnnouncementListResult(result: unknown, expectedPage: number, expectedPageSize: number) {
if (!isRecord(result) || !Array.isArray(result.announcements) || !Number.isInteger(result.total) || (result.total as number) < 0 || result.page !== expectedPage || result.pageSize !== expectedPageSize) {
throw new Error("公告列表返回格式不完整");
}
const announcements = result.announcements.map((value) => normalizeAnnouncementListItem(value));
if ((result.total as number) < announcements.length) throw new Error("公告列表总数与当前页数据不一致");
return { announcements, total: result.total as number, page: expectedPage, limit: expectedLimit };
return { announcements, total: result.total as number, page: expectedPage, pageSize: expectedPageSize };
}
function normalizeAnnouncementListItem(value: unknown): SystemAnnouncement {
@@ -1076,9 +1076,9 @@ async function inspectPendingReview(review: AnnouncementPendingReview) {
for (const title of queryTitles) {
let targetPage = 1;
while (targetPage <= 50) {
const data = assertAnnouncementListResult(await listAdminAnnouncements({ keyword: title, page: targetPage, limit: 100 }), targetPage, 100);
const data = assertAnnouncementListResult(await listAdminAnnouncements({ keyword: title, page: targetPage, pageSize: 100 }), targetPage, 100);
data.announcements.forEach((announcement) => candidates.set(announcement.id, announcement));
if (targetPage >= Math.max(1, Math.ceil(data.total / data.limit))) break;
if (targetPage >= Math.max(1, Math.ceil(data.total / data.pageSize))) break;
if (targetPage === 50) throw new Error("同名匹配记录过多,无法安全定位目标;请稍后重试。");
targetPage += 1;
}

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { App, Button, Descriptions, Drawer, Progress, Skeleton, Tabs } from "antd";
import { App, Button, Descriptions, Progress, Skeleton, Tabs } from "antd";
import { AppDrawer } from "@/components/ui/product/app-drawer";
import { IconButton } from "@/components/ui/base/buttons";
import { EmptyState } from "@/components/ui/product/empty-state";
import { ChevronLeft, ChevronRight } from "lucide-react";
@@ -46,7 +47,7 @@ export function AdminUserDetailDrawer({ userId, onClose, previousUserId, nextUse
useEffect(() => {
if (!userId) return;
let active = true;
void listAdminUserLedger(userId, { page: ledgerPage, limit: 20 })
void listAdminUserLedger(userId, { page: ledgerPage, pageSize: 20 })
.then((result) => {
if (active) {
setLedger(result.entries);
@@ -61,7 +62,7 @@ export function AdminUserDetailDrawer({ userId, onClose, previousUserId, nextUse
useEffect(() => {
if (!userId) return;
let active = true;
void listAdminUserTasks(userId, { page: taskPage, limit: 20 })
void listAdminUserTasks(userId, { page: taskPage, pageSize: 20 })
.then((result) => {
if (active) {
setTasks(result.tasks);
@@ -76,7 +77,7 @@ export function AdminUserDetailDrawer({ userId, onClose, previousUserId, nextUse
useEffect(() => {
if (!userId) return;
let active = true;
void listAdminUserAuditEvents(userId, { page: auditPage, limit: 20 })
void listAdminUserAuditEvents(userId, { page: auditPage, pageSize: 20 })
.then((result) => {
if (active) {
setEvents(result.events);
@@ -90,12 +91,11 @@ export function AdminUserDetailDrawer({ userId, onClose, previousUserId, nextUse
}, [auditPage, message, userId]);
return (
<Drawer
<AppDrawer
title={detail ? `${detail.user.displayName || detail.user.username} · 用户详情` : "用户详情"}
open={Boolean(userId)}
onClose={onClose}
size="min(920px, 100vw)"
destroyOnHidden
rootClassName="admin-drawer"
extra={onNavigate ? (
<div className="flex items-center gap-1">
@@ -229,7 +229,7 @@ export function AdminUserDetailDrawer({ userId, onClose, previousUserId, nextUse
) : (
<EmptyState size="compact" title="没有用户详情" />
)}
</Drawer>
</AppDrawer>
);
}

View File

@@ -134,7 +134,7 @@ export default function AnalyticsPanel({ users, channels }: Props) {
const searchUsers = async (keyword: string) => {
setSearchingUsers(true);
try {
const result = await listAdminUsers({ keyword: keyword.trim() || undefined, page: 1, limit: 50 });
const result = await listAdminUsers({ keyword: keyword.trim() || undefined, page: 1, pageSize: 50 });
setUserOptions(result.users);
} catch (error) {
message.error(error instanceof Error ? error.message : "搜索用户失败");

View File

@@ -1,5 +1,6 @@
import { useRef, useState } from "react";
import { Alert, App, Button, Form, Input, Modal, Segmented, Switch, Tabs } from "antd";
import { Alert, App, Button, Form, Input, Segmented, Switch, Tabs } from "antd";
import { AppModal } from "@/components/ui/product/app-modal";
import { FlaskConical, Plus } from "lucide-react";
import { ModelIconPicker } from "@/components/model-logo";
import { ModelIcon } from "@/components/model-picker";
@@ -166,7 +167,7 @@ export function ChannelModelEditor({
};
return (
<Modal
<AppModal
open
title={
<div className="admin-model-editor-title">
@@ -183,7 +184,7 @@ export function ChannelModelEditor({
keyboard={!busy}
closable={!busy}
onCancel={requestClose}
styles={{ container: { padding: 0, overflow: "hidden" }, body: { padding: 0, minHeight: 0, flex: 1 }, header: { margin: 0 }, footer: { margin: 0 } }}
flush styles={{ body: { minHeight: 0, flex: 1 }, header: { margin: 0 }, footer: { margin: 0 } }}
footer={
<div className="admin-model-editor-footer-actions">
<div className="admin-model-editor-footer-status">
@@ -396,7 +397,7 @@ export function ChannelModelEditor({
]}
/>
</Form>
</Modal>
</AppModal>
);
}

View File

@@ -1,6 +1,7 @@
import { App, Button, Empty, Modal, Spin } from "antd";
import { App, Button, Modal, Spin } from "antd";
import { ArrowDown, ArrowUp, GripVertical, ListOrdered } from "lucide-react";
import { useRef, useState } from "react";
import { EmptyState } from "@/components/ui/product/empty-state";
import { getChannelOrder, saveChannelOrder, type ChannelOrderItem } from "@/services/api/channel-order";
export function moveOrderItem<T extends { id: string }>(items: T[], id: string, target: number): T[] {
@@ -86,7 +87,7 @@ export function ChannelOrderDialog({ channelId, onSaved }: { channelId?: string;
<Spin spinning={loading}>
<div className="max-h-[55vh] overflow-y-auto space-y-2" role="list" aria-label="展示顺序">
{!loading && !items.length ? (
<Empty description="暂无可排序条目" />
<EmptyState size="compact" title="暂无可排序条目" />
) : (
items.map((item, index) => (
<div

View File

@@ -73,7 +73,7 @@ export default function CreditOperationsPanel({ users, activeOperation, onOperat
keyword: debouncedKeyword || undefined,
status: orderStatus,
page: targetPage,
limit: targetPageSize,
pageSize: targetPageSize,
});
if (requestId !== ordersRequestRef.current) return;
if (targetPage > 1 && result.total > 0 && result.orders.length === 0) {
@@ -134,7 +134,7 @@ export default function CreditOperationsPanel({ users, activeOperation, onOperat
if (activeOperation !== "adjustment") return;
const requestId = ++userSearchRequestRef.current;
setSearchingUsers(true);
void listAdminUsers({ keyword: debouncedAdjustmentSearch.trim() || undefined, page: 1, limit: 50 })
void listAdminUsers({ keyword: debouncedAdjustmentSearch.trim() || undefined, page: 1, pageSize: 50 })
.then((result) => {
if (requestId !== userSearchRequestRef.current) return;
const selectedId = adjustmentForm.getFieldValue("userId");

View File

@@ -79,7 +79,7 @@ export default function RedemptionCodesPanel({ createOpen, onCreateOpenChange, o
keyword: queryKeyword || undefined,
validity: queryValidity === "all" ? undefined : queryValidity,
page: targetPage,
limit: targetPageSize,
pageSize: targetPageSize,
});
if (requestId !== listRequestRef.current) return false;
const lastPage = Math.max(1, Math.ceil(result.total / targetPageSize));
@@ -641,7 +641,7 @@ function RedeemBatchCodesModal({ batch, onClose, onBatchChanged }: { batch: Rede
setCodes([]);
setTotal(0);
try {
const result = await listAdminRedeemBatchCodes(batch.id, { status: targetStatus === "all" ? undefined : targetStatus, page: targetPage, limit: targetPageSize });
const result = await listAdminRedeemBatchCodes(batch.id, { status: targetStatus === "all" ? undefined : targetStatus, page: targetPage, pageSize: targetPageSize });
if (requestId !== requestRef.current) return false;
const lastPage = Math.max(1, Math.ceil(result.total / targetPageSize));
if (targetPage > lastPage) {

View File

@@ -69,7 +69,7 @@ export default function StorageResourcesPanel() {
provider: provider === "all" ? undefined : provider,
userId: debouncedUserId || undefined,
page,
limit: pageSize,
pageSize: pageSize,
},
controller.signal,
)