fix(admin): harden bulk review interactions
This commit is contained in:
@@ -21,11 +21,11 @@ import {
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import { api } from '@/lib/api';
|
||||
import { api, errMsg } from '@/lib/api';
|
||||
import { canDo } from '@/lib/auth';
|
||||
import { formatWallTime } from '@/lib/format';
|
||||
import { refreshReviewBadge } from '@/lib/reviewBadge';
|
||||
import { failedReviewIds } from '@/lib/bulkAction';
|
||||
import { BULK_REVIEW_MAX, bulkFailureReasonText, failedReviewIds } from '@/lib/bulkAction';
|
||||
import type { BulkReviewResult } from '@/lib/bulkAction';
|
||||
import { usePagedList } from '@/lib/usePagedList';
|
||||
import type { Feedback, FeedbackSummary } from '@/lib/types';
|
||||
@@ -260,6 +260,10 @@ export default function FeedbacksPage() {
|
||||
|
||||
const openBulkModal = (action: 'approve' | 'reject') => {
|
||||
if (!selectedFeedbacks.length) return;
|
||||
if (selectedFeedbacks.length > BULK_REVIEW_MAX) {
|
||||
message.warning(`单次最多审核 ${BULK_REVIEW_MAX} 条反馈`);
|
||||
return;
|
||||
}
|
||||
bulkForm.resetFields();
|
||||
setBulkAction(action);
|
||||
};
|
||||
@@ -297,11 +301,13 @@ export default function FeedbacksPage() {
|
||||
const label = bulkAction === 'approve' ? '批量采纳' : '批量拒绝';
|
||||
if (failedIds.length) {
|
||||
message.warning(
|
||||
`${label}完成:成功 ${data.success} 条,失败 ${failedIds.length} 条;失败项已保留勾选`,
|
||||
`${label}完成:成功 ${data.success} 条,失败 ${failedIds.length} 条;失败项已保留勾选${bulkFailureReasonText(data)}`,
|
||||
);
|
||||
} else {
|
||||
message.success(`${label}完成:成功 ${data.success} 条`);
|
||||
}
|
||||
} catch (e) {
|
||||
message.error(errMsg(e));
|
||||
} finally {
|
||||
setBulkSubmitting(false);
|
||||
}
|
||||
@@ -487,14 +493,14 @@ export default function FeedbacksPage() {
|
||||
<Text>已选 <Text strong>{selectedFeedbacks.length}</Text> 条待审核反馈</Text>
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={!selectedFeedbacks.length}
|
||||
disabled={!selectedFeedbacks.length || selectedFeedbacks.length > BULK_REVIEW_MAX}
|
||||
onClick={() => openBulkModal('approve')}
|
||||
>
|
||||
批量采纳
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
disabled={!selectedFeedbacks.length}
|
||||
disabled={!selectedFeedbacks.length || selectedFeedbacks.length > BULK_REVIEW_MAX}
|
||||
onClick={() => openBulkModal('reject')}
|
||||
>
|
||||
批量拒绝
|
||||
@@ -513,7 +519,13 @@ export default function FeedbacksPage() {
|
||||
canReview
|
||||
? {
|
||||
selectedRowKeys,
|
||||
onChange: (keys) => setSelectedRowKeys(keys as number[]),
|
||||
onChange: (keys) => {
|
||||
const ids = keys as number[];
|
||||
if (ids.length > BULK_REVIEW_MAX) {
|
||||
message.warning(`单次最多选择 ${BULK_REVIEW_MAX} 条待审核反馈`);
|
||||
}
|
||||
setSelectedRowKeys(ids.slice(0, BULK_REVIEW_MAX));
|
||||
},
|
||||
getCheckboxProps: (record) => ({
|
||||
disabled: !isPending(record.status),
|
||||
name: `选择反馈 #${record.id}`,
|
||||
@@ -527,6 +539,7 @@ export default function FeedbacksPage() {
|
||||
pageSize,
|
||||
total,
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: [10, 20, 50],
|
||||
showTotal: (t) => `共 ${t} 条反馈`,
|
||||
onChange: onPageChange,
|
||||
}}
|
||||
|
||||
@@ -23,7 +23,7 @@ import { api, errMsg } from '@/lib/api';
|
||||
import { canDo } from '@/lib/auth';
|
||||
import { mediaUrl } from '@/lib/media';
|
||||
import { refreshReviewBadge } from '@/lib/reviewBadge';
|
||||
import { failedReviewIds } from '@/lib/bulkAction';
|
||||
import { BULK_REVIEW_MAX, bulkFailureReasonText, failedReviewIds } from '@/lib/bulkAction';
|
||||
import type { BulkReviewResult } from '@/lib/bulkAction';
|
||||
import { usePagedList } from '@/lib/usePagedList';
|
||||
import type { PriceReport, PriceReportSummary } from '@/lib/types';
|
||||
@@ -198,33 +198,45 @@ export default function PriceReportsPage() {
|
||||
loadSummary();
|
||||
};
|
||||
|
||||
const reportBulkResult = (label: string, succeeded: number, failedIds: number[]) => {
|
||||
const reportBulkResult = (label: string, result: BulkReviewResult) => {
|
||||
const failedIds = failedReviewIds(result);
|
||||
setSelectedRowKeys(failedIds);
|
||||
refreshReviewBadge('/price-reports');
|
||||
loadSummary();
|
||||
if (failedIds.length) reload();
|
||||
else onPageChange(1, pageSize);
|
||||
if (failedIds.length) {
|
||||
message.warning(`${label}完成:成功 ${succeeded} 条,失败 ${failedIds.length} 条;失败项已保留勾选`);
|
||||
message.warning(
|
||||
`${label}完成:成功 ${result.success} 条,失败 ${failedIds.length} 条;失败项已保留勾选${bulkFailureReasonText(result)}`,
|
||||
);
|
||||
} else {
|
||||
message.success(`${label}完成:成功 ${succeeded} 条`);
|
||||
message.success(`${label}完成:成功 ${result.success} 条`);
|
||||
}
|
||||
};
|
||||
|
||||
const bulkApprove = () => {
|
||||
const targets = selectedReports;
|
||||
if (!targets.length) return;
|
||||
if (targets.length > BULK_REVIEW_MAX) {
|
||||
message.warning(`单次最多审核 ${BULK_REVIEW_MAX} 条记录`);
|
||||
return;
|
||||
}
|
||||
modal.confirm({
|
||||
title: `确认批量通过 ${targets.length} 条低价上报?`,
|
||||
icon: <CheckCircleOutlined style={{ color: '#52c41a' }} />,
|
||||
content: `通过后将向每位用户发放 1000 金币,共发放 ${targets.length * 1000} 金币。请确认已逐条核实截图。`,
|
||||
okText: '批量通过并发金币',
|
||||
onOk: async () => {
|
||||
const { data } = await api.post<BulkReviewResult>(
|
||||
'/admin/api/price-reports/bulk/approve',
|
||||
{ ids: targets.map((item) => item.id) },
|
||||
);
|
||||
reportBulkResult('批量通过', data.success, failedReviewIds(data));
|
||||
try {
|
||||
const { data } = await api.post<BulkReviewResult>(
|
||||
'/admin/api/price-reports/bulk/approve',
|
||||
{ ids: targets.map((item) => item.id) },
|
||||
);
|
||||
reportBulkResult('批量通过', data);
|
||||
} catch (e) {
|
||||
message.error(errMsg(e));
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -262,6 +274,10 @@ export default function PriceReportsPage() {
|
||||
const confirmReject = async () => {
|
||||
const targets = bulkRejecting.length ? bulkRejecting : rejecting ? [rejecting] : [];
|
||||
if (!targets.length) return;
|
||||
if (targets.length > BULK_REVIEW_MAX) {
|
||||
message.warning(`单次最多审核 ${BULK_REVIEW_MAX} 条记录`);
|
||||
return;
|
||||
}
|
||||
const reason = rejectReason.trim();
|
||||
if (!reason) {
|
||||
message.warning('请填写拒绝理由');
|
||||
@@ -274,7 +290,7 @@ export default function PriceReportsPage() {
|
||||
'/admin/api/price-reports/bulk/reject',
|
||||
{ ids: targets.map((item) => item.id), reason },
|
||||
);
|
||||
reportBulkResult('批量拒绝', data.success, failedReviewIds(data));
|
||||
reportBulkResult('批量拒绝', data);
|
||||
} else {
|
||||
await api.post(`/admin/api/price-reports/${targets[0].id}/reject`, { reason });
|
||||
refreshReviewBadge('/price-reports');
|
||||
@@ -458,7 +474,7 @@ export default function PriceReportsPage() {
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<CheckCircleOutlined />}
|
||||
disabled={!selectedReports.length}
|
||||
disabled={!selectedReports.length || selectedReports.length > BULK_REVIEW_MAX}
|
||||
onClick={bulkApprove}
|
||||
>
|
||||
批量通过
|
||||
@@ -466,7 +482,7 @@ export default function PriceReportsPage() {
|
||||
<Button
|
||||
danger
|
||||
icon={<CloseCircleOutlined />}
|
||||
disabled={!selectedReports.length}
|
||||
disabled={!selectedReports.length || selectedReports.length > BULK_REVIEW_MAX}
|
||||
onClick={() => {
|
||||
setRejecting(null);
|
||||
setBulkRejecting(selectedReports);
|
||||
@@ -488,7 +504,13 @@ export default function PriceReportsPage() {
|
||||
canReview
|
||||
? {
|
||||
selectedRowKeys,
|
||||
onChange: (keys) => setSelectedRowKeys(keys as number[]),
|
||||
onChange: (keys) => {
|
||||
const ids = keys as number[];
|
||||
if (ids.length > BULK_REVIEW_MAX) {
|
||||
message.warning(`单次最多选择 ${BULK_REVIEW_MAX} 条待审核记录`);
|
||||
}
|
||||
setSelectedRowKeys(ids.slice(0, BULK_REVIEW_MAX));
|
||||
},
|
||||
getCheckboxProps: (record) => ({
|
||||
disabled: record.status !== 'pending',
|
||||
name: `选择低价上报 #${record.id}`,
|
||||
@@ -503,6 +525,7 @@ export default function PriceReportsPage() {
|
||||
pageSize,
|
||||
total,
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: [10, 20, 50],
|
||||
showTotal: (t) => `共 ${t} 条`,
|
||||
onChange: onPageChange,
|
||||
}}
|
||||
|
||||
@@ -12,6 +12,22 @@ export interface BulkReviewResult {
|
||||
items: BulkReviewItemResult[];
|
||||
}
|
||||
|
||||
export const BULK_REVIEW_MAX = 50;
|
||||
|
||||
export function failedReviewIds(result: BulkReviewResult): number[] {
|
||||
return result.items.filter((item) => !item.ok).map((item) => item.id);
|
||||
}
|
||||
|
||||
export function bulkFailureReasonText(result: BulkReviewResult): string {
|
||||
const reasons = Array.from(
|
||||
new Set(
|
||||
result.items
|
||||
.filter((item) => !item.ok)
|
||||
.map((item) => item.error?.trim())
|
||||
.filter((reason): reason is string => Boolean(reason)),
|
||||
),
|
||||
);
|
||||
if (!reasons.length) return '';
|
||||
const visible = reasons.slice(0, 3);
|
||||
return `;原因:${visible.join(';')}${reasons.length > visible.length ? ';…' : ''}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user