feat: 协作台账支持归档并精简状态列

This commit is contained in:
yml2213
2026-10-19 13:04:27 +08:00
parent 375567cbd2
commit 781b27e63a
11 changed files with 1040 additions and 66 deletions
+21 -3
View File
@@ -35,6 +35,9 @@ import type {
HuyaCollabActivityBatchResult,
HuyaCollabActivityItem,
HuyaCollabActivityScope,
HuyaCollabArchiveRequest,
HuyaCollabArchiveResult,
HuyaCollabArchiveScope,
HuyaCollabCkExportResult,
HuyaCollabCkPreview,
HuyaCollabCkRequest,
@@ -203,9 +206,12 @@ export const huyaApi = {
/** 列表:共享视图,默认不按人过滤(负责人只是筛选维度) */
listCollabOrders: (params: HuyaCollabOrderQuery = {}) =>
api.get<HuyaCollabOrderList, HuyaCollabOrderList>('/huya/external-orders', { params }),
/** 页头统计条:各业务状态计数 + CK 失效数 */
collabOrdersSummary: () =>
api.get<HuyaCollabSummary, HuyaCollabSummary>('/huya/external-orders/summary'),
/** 页头统计条:口径与当前归档/作废过滤一致 */
collabOrdersSummary: (params: {
include_void?: boolean;
archive_scope?: HuyaCollabArchiveScope;
} = {}) =>
api.get<HuyaCollabSummary, HuyaCollabSummary>('/huya/external-orders/summary', { params }),
/** 负责人候选(全部活跃用户) */
collabOrderOwners: () =>
api.get<HuyaCollabOwner[], HuyaCollabOwner[]>('/huya/external-orders/owners'),
@@ -278,6 +284,18 @@ export const huyaApi = {
'/huya/external-orders/status/batch',
data,
),
/** 归档抢购成功的订单(可带备注;archive_filtered 时按当前筛选批量归档) */
archiveCollabOrders: (data: HuyaCollabArchiveRequest) =>
api.post<HuyaCollabArchiveResult, HuyaCollabArchiveResult>(
'/huya/external-orders/archive',
data,
),
/** 取消归档,恢复回主台账 */
unarchiveCollabOrders: (ids: number[]) =>
api.post<HuyaCollabArchiveResult, HuyaCollabArchiveResult>(
'/huya/external-orders/unarchive',
{ ids },
),
/** 查这一单的积分 + 兑换记录(写一条生命周期快照,已上脚本的单会按结果自动改状态) */
queryCollabOrderActivity: (id: number) =>
api.post<HuyaCollabActivityItem, HuyaCollabActivityItem>(
+42
View File
@@ -1214,6 +1214,9 @@ export type HuyaCollabStatus =
/** 凭据状态:CK 本身还有没有效 */
export type HuyaCollabCkStatus = 'unverified' | 'valid' | 'invalid' | 'disabled';
/** 归档可见范围:默认活跃;查看归档时只返回已归档行 */
export type HuyaCollabArchiveScope = 'active' | 'archived' | 'all';
export type HuyaCollabImageSlot = 'bind_images' | 'home_images';
/** 一行 = 一单 = 一个外部虎牙 CK */
@@ -1258,6 +1261,12 @@ export interface HuyaCollabOrder {
scripted_at: string | null;
done_at: string | null;
void_reason: string;
/** 归档是可见性状态,不改业务状态;归档行只读 */
is_archived: boolean;
archived_at: string | null;
archived_by: number | null;
archived_by_name: string;
archive_reason: string;
points: number | null;
elite_opened: boolean | null;
snapshot_at: string | null;
@@ -1421,6 +1430,36 @@ export interface HuyaCollabStatusBatchResult {
results: HuyaCollabStatusBatchItem[];
}
/** 归档请求:ids 为空且 archive_filtered=true 时按筛选范围归档 */
export interface HuyaCollabArchiveRequest {
ids?: number[];
reason?: string;
archive_filtered?: boolean;
expected_count?: number;
shop?: string;
owner_id?: number | null;
status?: string;
ck_status?: string;
gift_name?: string;
keyword?: string;
include_void?: boolean;
}
export interface HuyaCollabArchiveItem {
id: number;
order_no: string;
ok: boolean;
message: string;
}
export interface HuyaCollabArchiveResult {
total: number;
archived: number;
unarchived: number;
skipped: number;
results: HuyaCollabArchiveItem[];
}
/** 导出 CK 里的一行(ck_text 是**明文凭据**,只有导出接口会返回) */
export interface HuyaCollabCkExportItem {
id: number;
@@ -1460,6 +1499,8 @@ export interface HuyaCollabSummary {
total: number;
by_status: Record<string, number>;
ck_invalid: number;
/** 全量已归档数量(不受当前 archive_scope 影响,用于入口角标) */
archived_total: number;
}
export interface HuyaCollabOwner {
@@ -1484,6 +1525,7 @@ export interface HuyaCollabOrderQuery {
gift_name?: string;
keyword?: string;
include_void?: boolean;
archive_scope?: HuyaCollabArchiveScope;
/** 服务端排序字段(后端白名单) */
sort_by?: string;
sort_order?: 'asc' | 'desc';
+271 -32
View File
@@ -15,7 +15,7 @@ import {
import type { TableProps } from 'antd';
import {
CheckCircleOutlined, CloudUploadOutlined, CopyOutlined, DeleteOutlined, EditOutlined, FileAddOutlined,
FileExcelOutlined, GiftOutlined, LinkOutlined, PlusOutlined, ReloadOutlined, SafetyCertificateOutlined,
FileExcelOutlined, GiftOutlined, InboxOutlined, LinkOutlined, PlusOutlined, ReloadOutlined, SafetyCertificateOutlined,
ShopOutlined, StopOutlined, SwapOutlined, SyncOutlined, UndoOutlined,
} from '@ant-design/icons';
import {
@@ -500,7 +500,10 @@ export default function HuyaCollabPage() {
// 「常用礼包」:台账里用得最多的几个(新建/补资料的礼包名快捷按钮,见 2026-10-18 需求)
const [frequentGifts, setFrequentGifts] = useState<HuyaCollabFrequentGift[]>([]);
const [shopModalOpen, setShopModalOpen] = useState(false);
const [filters, setFilters] = useState<HuyaCollabOrderQuery>({ include_void: false });
const [filters, setFilters] = useState<HuyaCollabOrderQuery>({
include_void: false,
archive_scope: 'active',
});
// 默认按固定创建序号正序:老单在上、新单永远追加在最后。
const [sortKey, setSortKey] = useState<CollabSortKey>('seq');
const [sortOrder, setSortOrder] = useState<CollabSortOrder>('ascend');
@@ -563,6 +566,13 @@ export default function HuyaCollabPage() {
const [batchStatusTarget, setBatchStatusTarget] = useState<HuyaCollabStatus>('bound');
const [batchStatusReason, setBatchStatusReason] = useState('');
const [batchStatusRollback, setBatchStatusRollback] = useState(false);
// 归档弹窗:ids=null 表示按当前筛选结果批量归档
const [archiveOpen, setArchiveOpen] = useState(false);
const [archiveIds, setArchiveIds] = useState<number[] | null>(null);
const [archiveFiltered, setArchiveFiltered] = useState(false);
const [archiveExpectedCount, setArchiveExpectedCount] = useState(0);
const [archiveReason, setArchiveReason] = useState('');
const [archiving, setArchiving] = useState(false);
// 积分 / 兑换记录(「账号全生命周期」)
const [querying, setQuerying] = useState(false);
const [metrics, setMetrics] = useState<HuyaCollabMetric[]>([]);
@@ -593,7 +603,10 @@ export default function HuyaCollabPage() {
page,
page_size: pageSize,
}),
huyaApi.collabOrdersSummary(),
huyaApi.collabOrdersSummary({
include_void: filters.include_void,
archive_scope: filters.archive_scope ?? 'active',
}),
withMeta ? huyaApi.collabOrderOwners() : Promise.resolve(null),
withMeta ? huyaApi.listCollabShops() : Promise.resolve(null),
withMeta ? huyaApi.collabOrderGifts() : Promise.resolve(null),
@@ -621,7 +634,7 @@ export default function HuyaCollabPage() {
// 暂停后连 WebSocket 也不建:既不自动重拉,也不做断线兜底轮询。
const realtimeSyncEnabled = realtimeEnabled && !realtimePaused;
const collabModalOpen = Boolean(
createOpen || supplementTarget || detail || statusAction || batchStatusOpen || ckTarget || ckExportOpen || shopModalOpen,
createOpen || supplementTarget || detail || statusAction || batchStatusOpen || archiveOpen || ckTarget || ckExportOpen || shopModalOpen,
);
const toggleRealtimeSync = (nextEnabled: boolean) => {
@@ -774,6 +787,122 @@ export default function HuyaCollabPage() {
[runCkPreview],
);
const isArchiveView = filters.archive_scope === 'archived';
const selectedRows = useMemo(
() => rows.filter((row) => selectedIds.includes(row.id)),
[rows, selectedIds],
);
const selectedArchivable = selectedRows.length > 0
&& selectedRows.every((row) => row.status === 'done' && !row.is_archived);
const canArchiveFiltered = canEdit && !isArchiveView && filters.status === 'done' && total > 0;
const toggleArchiveView = (checked: boolean) => {
setPage(1);
setSelectedIds([]);
setFilters((prev) => ({
...prev,
archive_scope: checked ? 'archived' : 'active',
// 归档视图目前只有「抢购成功」;保留原来的状态筛选会看起来像空表。
status: undefined,
}));
};
const openArchiveModal = (
ids: number[] | null,
options: { filtered?: boolean; count?: number } = {},
) => {
setArchiveIds(ids);
setArchiveFiltered(options.filtered === true);
setArchiveExpectedCount(options.count ?? ids?.length ?? 0);
setArchiveReason('');
setArchiveOpen(true);
};
const submitArchive = async () => {
if (!archiveFiltered && (!archiveIds || archiveIds.length === 0)) return;
setArchiving(true);
try {
const result = await huyaApi.archiveCollabOrders(
archiveFiltered
? {
archive_filtered: true,
expected_count: archiveExpectedCount,
reason: archiveReason,
shop: filters.shop || '',
owner_id: filters.owner_id ?? null,
status: filters.status || '',
ck_status: filters.ck_status || '',
gift_name: filters.gift_name || '',
keyword: filters.keyword || '',
include_void: !!filters.include_void,
}
: { ids: archiveIds ?? [], reason: archiveReason },
);
const failures = result.results.filter((item) => !item.ok);
if (result.archived > 0) {
message.success(`已归档 ${result.archived} 条抢购成功订单`);
} else {
message.warning('没有可归档的订单');
}
if (failures.length > 0) {
modal.info({
title: `归档完成:成功 ${result.archived} / 跳过 ${result.skipped}`,
width: 640,
content: (
<Space direction="vertical" style={{ width: '100%' }}>
{failures.map((item) => (
<Text key={item.id} type="secondary">
{item.order_no}:{item.message}
</Text>
))}
</Space>
),
});
}
setArchiveOpen(false);
setArchiveIds(null);
setArchiveFiltered(false);
setArchiveReason('');
setSelectedIds([]);
setPage(1);
void load();
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setArchiving(false);
}
};
const unarchiveMany = async (ids: number[]) => {
if (ids.length === 0) return;
setBusy(true);
try {
const result = await huyaApi.unarchiveCollabOrders(ids);
if (result.unarchived > 0) {
message.success(`已取消归档 ${result.unarchived} 条`);
} else {
message.warning('没有可取消归档的订单');
}
setSelectedIds([]);
if (detail && ids.includes(detail.id)) setDetail(null);
void load();
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setBusy(false);
}
};
const confirmUnarchive = (ids: number[]) => {
if (ids.length === 0) return;
modal.confirm({
title: ids.length === 1 ? '取消归档这一单?' : `取消归档 ${ids.length} 条?`,
content: '取消后会恢复到活跃台账,之前的归档备注仍保留在审计时间线里。',
okText: '取消归档',
onOk: () => unarchiveMany(ids),
});
};
/** 勾选多行一次改状态(往前可跳步;回退要在弹窗里勾「回退」) */
const submitBatchStatus = async () => {
setBusy(true);
@@ -1707,7 +1836,7 @@ export default function HuyaCollabPage() {
/>
}
>
<Button size="small" type={value ? 'text' : 'link'} disabled={!canEdit}>
<Button size="small" type={value ? 'text' : 'link'} disabled={!canEdit || row.is_archived}>
{value || '+ 选店铺'}
</Button>
</Popover>
@@ -1719,7 +1848,7 @@ export default function HuyaCollabPage() {
render: (value: number | null, row) => (
<Select
size="small" variant="borderless" style={{ width: '100%' }}
value={value ?? undefined} options={ownerOptions} disabled={!canEdit}
value={value ?? undefined} options={ownerOptions} disabled={!canEdit || row.is_archived}
placeholder="+ 指派" allowClear showSearch optionFilterProp="label"
onChange={(next: number | undefined) => void patchOrder(row, { owner_id: next ?? null })}
/>
@@ -1832,7 +1961,7 @@ export default function HuyaCollabPage() {
optionFilterProp="label"
options={giftSelectOptions(row.gift_name)}
value={row.gift_name || undefined}
disabled={!canEdit}
disabled={!canEdit || row.is_archived}
placeholder="+ 点选礼包"
popupMatchSelectWidth={false}
// 前缀单独一行、礼包名完整换行,别让单行省略号把名字吃掉
@@ -1841,7 +1970,7 @@ export default function HuyaCollabPage() {
notFoundContent={giftOptions.length === 0 ? '礼包缓存为空:先在「新建」里贴 CK 拉一次' : '没有匹配的礼包'}
onChange={(next: string) => void patchOrder(row, { gift_name: next })}
/>
<EditableText value={row.remark} disabled={!canEdit} placeholder="+ 备注"
<EditableText value={row.remark} disabled={!canEdit || row.is_archived} placeholder="+ 备注"
onCommit={(next) => void patchOrder(row, { remark: next })} />
</Space>
),
@@ -1850,15 +1979,15 @@ export default function HuyaCollabPage() {
title: '状态', dataIndex: 'status', width: 130,
...sortProps('status'),
render: (value: HuyaCollabStatus, row) => (
<Space direction="vertical" size={2}>
<Space size={4} wrap>
<Tag color={STATUS_META[value]?.color}>{row.status_label || STATUS_META[value]?.label || value}</Tag>
{row.next_status && canEdit && (
<Popconfirm title={`标记为「${STATUS_META[row.next_status].label}」?`}
onConfirm={() => void advance(row)} disabled={busy}>
<Button size="small" type="link" style={{ padding: 0 }}>
→ {STATUS_META[row.next_status].label}
</Button>
</Popconfirm>
{row.is_archived && (
<Tooltip
title={`归档于 ${formatTime(row.archived_at)} · ${row.archived_by_name || '-'}`
+ (row.archive_reason ? `\n备注:${row.archive_reason}` : '')}
>
<Tag color="default">已归档</Tag>
</Tooltip>
)}
</Space>
),
@@ -1941,13 +2070,13 @@ export default function HuyaCollabPage() {
title: '操作', width: 300, fixed: 'right', align: 'center',
render: (_, row) => (
<Space size={4} wrap>
{canEdit && row.status === 'incomplete' && (
{canEdit && row.status === 'incomplete' && !row.is_archived && (
<Button size="small" type="primary" ghost icon={<FileAddOutlined />}
onClick={() => openSupplement(row)}>
补资料
</Button>
)}
{isAdmin && (
{isAdmin && !row.is_archived && (
<Tooltip title={row.has_cookie ? '复制这一行的 CK 明文(仅管理员)' : '这行还没贴 CK'}>
<Button size="small" icon={<CopyOutlined />}
disabled={busy || !row.has_cookie}
@@ -1957,7 +2086,13 @@ export default function HuyaCollabPage() {
</Tooltip>
)}
<Button size="small" icon={<EditOutlined />} onClick={() => void openDetail(row)}>详情</Button>
{canEdit && (
{canEdit && row.is_archived && (
<Button size="small" icon={<UndoOutlined />} disabled={busy}
onClick={() => confirmUnarchive([row.id])}>
取消归档
</Button>
)}
{canEdit && !row.is_archived && (
<Dropdown
menu={{
items: [
@@ -1970,6 +2105,12 @@ export default function HuyaCollabPage() {
{ key: 'set', label: '改状态…', icon: <CheckCircleOutlined /> },
{ key: 'rollback', label: '回退状态…', icon: <UndoOutlined /> },
{ key: 'void', label: '作废…', icon: <StopOutlined />, danger: true },
...(row.status === 'done'
? [
{ type: 'divider' as const },
{ key: 'archive', label: '归档…', icon: <InboxOutlined /> },
]
: []),
...(canManage
? [{ key: 'delete', label: '删除这一行', icon: <DeleteOutlined />, danger: true }]
: []),
@@ -1980,6 +2121,7 @@ export default function HuyaCollabPage() {
if (key === 'ck') setCkTarget({ order: row, text: '' });
if (key === 'rebuild') setCkTarget({ order: row, text: '' });
if (key === 'disable') void toggleCkDisabled(row);
if (key === 'archive') openArchiveModal([row.id], { count: 1 });
if (key === 'set') {
setRollbackTarget(row.next_status ?? row.status);
setStatusReason('');
@@ -2146,18 +2288,50 @@ export default function HuyaCollabPage() {
<Switch size="small" checked={!!filters.include_void}
onChange={(checked) => { setPage(1); setFilters((prev) => ({ ...prev, include_void: checked })); }} />
</Space>
<Space size={4}>
<Text type="secondary">查看归档</Text>
<Switch size="small" checked={isArchiveView} onChange={toggleArchiveView} />
<Tag>{summary?.archived_total ?? 0}</Tag>
</Space>
</Space>
{/* 第二行:动作(固定在这一行;勾选后加「(N)」只是按钮变宽,不会把列表顶下去) */}
<Space wrap style={{ width: '100%' }}>
<Button icon={<ReloadOutlined />} onClick={() => void load()} disabled={loading}>刷新</Button>
{realtimePending && <Tag color="processing">有更新待同步</Tag>}
{canEdit && (
{canEdit && !isArchiveView && (
<Button
icon={<InboxOutlined />}
disabled={!selectedArchivable || busy}
onClick={() => openArchiveModal(selectedIds, { count: selectedRows.length })}
>
批量归档{selectedIds.length > 0 ? `(${selectedIds.length})` : ''}
</Button>
)}
{canArchiveFiltered && (
<Button
icon={<InboxOutlined />}
disabled={busy}
onClick={() => openArchiveModal(null, { filtered: true, count: total })}
>
归档当前筛选结果({total})
</Button>
)}
{canEdit && isArchiveView && (
<Button
icon={<UndoOutlined />}
disabled={selectedIds.length === 0 || busy}
onClick={() => confirmUnarchive(selectedIds)}
>
批量取消归档{selectedIds.length > 0 ? `(${selectedIds.length})` : ''}
</Button>
)}
{canEdit && !isArchiveView && (
<Button icon={<SafetyCertificateOutlined />} disabled={selectedIds.length === 0 || busy}
onClick={() => void verifySelected()}>
批量验证 CK{selectedIds.length > 0 ? `(${selectedIds.length})` : ''}
</Button>
)}
{canEdit && (
{canEdit && !isArchiveView && (
// 勾选多行一次改状态(往前可跳步;回退在弹窗里勾「回退」)
<Dropdown
disabled={selectedIds.length === 0 || busy}
@@ -2179,7 +2353,7 @@ export default function HuyaCollabPage() {
</Button>
</Dropdown>
)}
{canEdit && (
{canEdit && !isArchiveView && (
// 查积分 / 查兑换记录**拆成两个按钮**(2026-10-17):查积分只写积分快照,
// 既不判定也不自动改状态;只有查兑换记录才产出判定并可能自动改状态。
// 合成一个按钮时「只想要积分」也会顺带把状态改了,两边互相干扰。
@@ -2196,7 +2370,7 @@ export default function HuyaCollabPage() {
</Button>
</>
)}
{canExportCk && (
{canExportCk && !isArchiveView && (
// 导出 CK 到剪贴板(勾选几行就导几行;明文只在这个弹窗里出现)
<Button icon={<CopyOutlined />} disabled={selectedIds.length === 0 || busy}
loading={ckExporting}
@@ -2204,15 +2378,15 @@ export default function HuyaCollabPage() {
导出 CK{selectedIds.length > 0 ? `(${selectedIds.length})` : ''}
</Button>
)}
{canEdit && (
{canEdit && !isArchiveView && (
<Button icon={<FileExcelOutlined />} onClick={() => { setImportResult(null); setImportOpen(true); }}>
粘贴导入
</Button>
)}
{canEdit && (
{canEdit && !isArchiveView && (
<Button icon={<ShopOutlined />} onClick={() => setShopModalOpen(true)}>店铺管理</Button>
)}
{canEdit && (
{canEdit && !isArchiveView && (
<Button
type="primary"
icon={<PlusOutlined />}
@@ -2246,6 +2420,14 @@ export default function HuyaCollabPage() {
</Button>
)}
</Space>
{isArchiveView && (
<Alert
type="info"
showIcon
message={`正在查看已归档台账(${summary?.total ?? 0} 条,只读)`}
description="归档行仍可搜索、看详情和审计;需要修改或重新操作时,先点「取消归档」恢复回活跃台账。"
/>
)}
</Space>
</Card>
@@ -2293,11 +2475,29 @@ export default function HuyaCollabPage() {
width={680}
open={!!detail}
onClose={() => setDetail(null)}
title={detail ? `订单 ${detail.order_no}` : ''}
title={detail
? <Space>订单 {detail.order_no}{detail.is_archived && <Tag color="default">已归档</Tag>}</Space>
: ''}
destroyOnHidden
>
{detail && (
<Space direction="vertical" size={12} style={{ width: '100%' }}>
{detail.is_archived && (
<Alert
type="info"
showIcon
message={`这单已归档 · ${formatTime(detail.archived_at)} · ${detail.archived_by_name || '-'}`}
description={detail.archive_reason
? `归档备注:${detail.archive_reason}。归档期间只读;需要继续操作请先取消归档。`
: '归档期间只读;需要继续操作请先取消归档。'}
action={canEdit ? (
<Button size="small" icon={<UndoOutlined />} disabled={busy}
onClick={() => confirmUnarchive([detail.id])}>
取消归档
</Button>
) : undefined}
/>
)}
{detail.status === 'incomplete' && (
<Alert
type="warning"
@@ -2334,9 +2534,16 @@ export default function HuyaCollabPage() {
{ key: 'snapshot_at', label: '绑定快照时间', children: formatTime(detail.bind_snapshot_at) },
{ key: 'status', label: '业务状态',
children: <Tag color={STATUS_META[detail.status]?.color}>{detail.status_label}</Tag> },
...(detail.is_archived
? [
{ key: 'archived_at', label: '归档时间', children: formatTime(detail.archived_at) },
{ key: 'archived_by', label: '归档人 / 备注',
children: `${detail.archived_by_name || '-'}${detail.archive_reason ? ` · ${detail.archive_reason}` : ''}` },
]
: []),
{ key: 'bound_at', label: '绑定 / 充值时间',
children: `${formatTime(detail.bound_at)} / ${formatTime(detail.recharged_at)}` },
{ key: 'scripted_at', label: '上脚本 / 归档时间',
{ key: 'scripted_at', label: '上脚本 / 抢购成功时间',
children: `${formatTime(detail.scripted_at)} / ${formatTime(detail.done_at)}` },
{ key: 'updated', label: '最后改动',
children: `${detail.updated_by_name || '-'} · ${formatTime(detail.updated_at)}` },
@@ -2379,7 +2586,7 @@ export default function HuyaCollabPage() {
</>
)}
{canEdit && (
{canEdit && !detail.is_archived && (
<>
<Divider style={{ margin: '4px 0' }}>可编辑字段</Divider>
<Form
@@ -2446,7 +2653,7 @@ export default function HuyaCollabPage() {
<ImageSlotEditor
order={detail}
slot="home_images"
canEdit={canEdit}
canEdit={canEdit && !detail.is_archived}
busy={busy}
onUpload={(targetSlot, file) => void uploadImage(detail, targetSlot, file)}
onRemove={(targetSlot, key) => void removeImage(detail, targetSlot, key)}
@@ -2477,7 +2684,7 @@ export default function HuyaCollabPage() {
</>
)}
{canEdit && (
{canEdit && !detail.is_archived && (
<Space wrap>
{detail.next_status && (
<Button icon={<CheckCircleOutlined />} disabled={busy}
@@ -2531,7 +2738,7 @@ export default function HuyaCollabPage() {
<Divider style={{ margin: '4px 0' }}>账号全生命周期(积分 / 兑换记录)</Divider>
<Space wrap size={8}>
{canEdit && (
{canEdit && !detail.is_archived && (
<Button icon={<SyncOutlined />} disabled={busy || querying || !detail.has_cookie}
loading={querying}
onClick={() => void queryActivityOne(detail)}>
@@ -3028,6 +3235,38 @@ export default function HuyaCollabPage() {
</Space>
</Modal>
<Modal
title={archiveFiltered
? `归档当前筛选结果(${archiveExpectedCount} 条)`
: `归档抢购成功订单(${archiveIds?.length ?? 0} 条)`}
open={archiveOpen}
onCancel={() => setArchiveOpen(false)}
onOk={() => void submitArchive()}
confirmLoading={archiving}
okText={`归档 ${archiveFiltered ? archiveExpectedCount : (archiveIds?.length ?? 0)} 条`}
destroyOnHidden
>
<Space direction="vertical" size={10} style={{ width: '100%' }}>
<Alert
type="info"
showIcon
message="归档只是从活跃台账隐藏,不会删除数据"
description="业务状态仍保持「抢购成功」;归档后只读,可在「查看归档」里搜索,取消归档即可恢复。"
/>
<Input.TextArea
rows={3}
maxLength={256}
showCount
value={archiveReason}
onChange={(event) => setArchiveReason(event.target.value)}
placeholder="归档备注(可选):例如 已完成对账、活动结束、客户已验收…"
/>
<Text type="secondary" style={{ fontSize: 12 }}>
只有「抢购成功」的活跃订单可以归档;失败的订单请先处理或作废。
</Text>
</Space>
</Modal>
<Modal
title={`批量改状态(已选 ${selectedIds.length} 条)`}
open={batchStatusOpen}