Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aff250f9fd |
@@ -25,7 +25,8 @@ import { api } from '@/lib/api';
|
||||
import { canDo } from '@/lib/auth';
|
||||
import { formatWallTime } from '@/lib/format';
|
||||
import { refreshReviewBadge } from '@/lib/reviewBadge';
|
||||
import { runBulkAction } from '@/lib/bulkAction';
|
||||
import { failedReviewIds } from '@/lib/bulkAction';
|
||||
import type { BulkReviewResult } from '@/lib/bulkAction';
|
||||
import { usePagedList } from '@/lib/usePagedList';
|
||||
import type { Feedback, FeedbackSummary } from '@/lib/types';
|
||||
import FeedbackHandleDrawer from './FeedbackHandleDrawer';
|
||||
@@ -269,33 +270,37 @@ export default function FeedbacksPage() {
|
||||
const targets = selectedFeedbacks;
|
||||
setBulkSubmitting(true);
|
||||
try {
|
||||
const result = await runBulkAction(targets, (item) =>
|
||||
const { data } = await api.post<BulkReviewResult>(
|
||||
`/admin/api/feedbacks/bulk/${bulkAction}`,
|
||||
bulkAction === 'approve'
|
||||
? api.post(`/admin/api/feedbacks/${item.id}/approve`, {
|
||||
? {
|
||||
ids: targets.map((item) => item.id),
|
||||
reward_coins: values.reward_coins,
|
||||
note: values.note?.trim() || null,
|
||||
reply: values.reply?.trim() || null,
|
||||
})
|
||||
: api.post(`/admin/api/feedbacks/${item.id}/reject`, {
|
||||
}
|
||||
: {
|
||||
ids: targets.map((item) => item.id),
|
||||
reason: values.reason.trim(),
|
||||
note: values.note?.trim() || null,
|
||||
reply: values.reply?.trim() || null,
|
||||
}),
|
||||
},
|
||||
);
|
||||
setSelectedRowKeys(result.failedIds);
|
||||
const failedIds = failedReviewIds(data);
|
||||
setSelectedRowKeys(failedIds);
|
||||
setBulkAction(null);
|
||||
bulkForm.resetFields();
|
||||
if (result.failedIds.length) reload();
|
||||
if (failedIds.length) reload();
|
||||
else onPageChange(1, pageSize);
|
||||
loadSummary();
|
||||
refreshReviewBadge('/feedbacks');
|
||||
const label = bulkAction === 'approve' ? '批量采纳' : '批量拒绝';
|
||||
if (result.failedIds.length) {
|
||||
if (failedIds.length) {
|
||||
message.warning(
|
||||
`${label}完成:成功 ${result.succeededIds.length} 条,失败 ${result.failedIds.length} 条;失败项已保留勾选`,
|
||||
`${label}完成:成功 ${data.success} 条,失败 ${failedIds.length} 条;失败项已保留勾选`,
|
||||
);
|
||||
} else {
|
||||
message.success(`${label}完成:成功 ${result.succeededIds.length} 条`);
|
||||
message.success(`${label}完成:成功 ${data.success} 条`);
|
||||
}
|
||||
} finally {
|
||||
setBulkSubmitting(false);
|
||||
|
||||
@@ -23,7 +23,8 @@ import { api, errMsg } from '@/lib/api';
|
||||
import { canDo } from '@/lib/auth';
|
||||
import { mediaUrl } from '@/lib/media';
|
||||
import { refreshReviewBadge } from '@/lib/reviewBadge';
|
||||
import { runBulkAction } from '@/lib/bulkAction';
|
||||
import { failedReviewIds } from '@/lib/bulkAction';
|
||||
import type { BulkReviewResult } from '@/lib/bulkAction';
|
||||
import { usePagedList } from '@/lib/usePagedList';
|
||||
import type { PriceReport, PriceReportSummary } from '@/lib/types';
|
||||
import UserRecordsDrawer from '@/components/UserRecordsDrawer';
|
||||
@@ -219,10 +220,11 @@ export default function PriceReportsPage() {
|
||||
content: `通过后将向每位用户发放 1000 金币,共发放 ${targets.length * 1000} 金币。请确认已逐条核实截图。`,
|
||||
okText: '批量通过并发金币',
|
||||
onOk: async () => {
|
||||
const result = await runBulkAction(targets, (item) =>
|
||||
api.post(`/admin/api/price-reports/${item.id}/approve`),
|
||||
const { data } = await api.post<BulkReviewResult>(
|
||||
'/admin/api/price-reports/bulk/approve',
|
||||
{ ids: targets.map((item) => item.id) },
|
||||
);
|
||||
reportBulkResult('批量通过', result.succeededIds.length, result.failedIds);
|
||||
reportBulkResult('批量通过', data.success, failedReviewIds(data));
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -268,10 +270,11 @@ export default function PriceReportsPage() {
|
||||
setRejectSubmitting(true);
|
||||
try {
|
||||
if (bulkRejecting.length) {
|
||||
const result = await runBulkAction(targets, (item) =>
|
||||
api.post(`/admin/api/price-reports/${item.id}/reject`, { reason }),
|
||||
const { data } = await api.post<BulkReviewResult>(
|
||||
'/admin/api/price-reports/bulk/reject',
|
||||
{ ids: targets.map((item) => item.id), reason },
|
||||
);
|
||||
reportBulkResult('批量拒绝', result.succeededIds.length, result.failedIds);
|
||||
reportBulkResult('批量拒绝', data.success, failedReviewIds(data));
|
||||
} else {
|
||||
await api.post(`/admin/api/price-reports/${targets[0].id}/reject`, { reason });
|
||||
refreshReviewBadge('/price-reports');
|
||||
|
||||
+14
-28
@@ -1,31 +1,17 @@
|
||||
export interface BulkActionResult {
|
||||
succeededIds: number[];
|
||||
failedIds: number[];
|
||||
export interface BulkReviewItemResult {
|
||||
id: number;
|
||||
ok: boolean;
|
||||
status: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run per-record write endpoints with bounded concurrency.
|
||||
*
|
||||
* The review endpoints already own transaction, audit and idempotency semantics,
|
||||
* so the admin UI deliberately keeps using them instead of duplicating that logic.
|
||||
*/
|
||||
export async function runBulkAction<T extends { id: number }>(
|
||||
targets: T[],
|
||||
action: (target: T) => Promise<unknown>,
|
||||
concurrency = 5,
|
||||
): Promise<BulkActionResult> {
|
||||
const succeededIds: number[] = [];
|
||||
const failedIds: number[] = [];
|
||||
|
||||
for (let index = 0; index < targets.length; index += concurrency) {
|
||||
const chunk = targets.slice(index, index + concurrency);
|
||||
const settled = await Promise.allSettled(chunk.map(action));
|
||||
settled.forEach((result, resultIndex) => {
|
||||
const id = chunk[resultIndex].id;
|
||||
if (result.status === 'fulfilled') succeededIds.push(id);
|
||||
else failedIds.push(id);
|
||||
});
|
||||
}
|
||||
|
||||
return { succeededIds, failedIds };
|
||||
export interface BulkReviewResult {
|
||||
total: number;
|
||||
success: number;
|
||||
failed: number;
|
||||
items: BulkReviewItemResult[];
|
||||
}
|
||||
|
||||
export function failedReviewIds(result: BulkReviewResult): number[] {
|
||||
return result.items.filter((item) => !item.ok).map((item) => item.id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user