feat: 协作台账支持归档并精简状态列
This commit is contained in:
@@ -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>(
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user