feat(douyu): 支持批量导出用户名

This commit is contained in:
yml2213
2026-10-23 09:52:47 +08:00
parent bf2f120fdc
commit f074b75871
6 changed files with 322 additions and 1 deletions
+27
View File
@@ -130,6 +130,28 @@ export interface DouyuCollabCkExportResult {
items: DouyuCollabCkExportItem[];
}
export interface DouyuCollabUsernameExportItem {
id: number;
order_no: string;
account_username: string;
}
export interface DouyuCollabUsernameExportSkipped {
id: number;
order_no: string;
reason: string;
}
export interface DouyuCollabUsernameExportResult {
total: number;
exported: number;
unique: number;
skipped: DouyuCollabUsernameExportSkipped[];
items: DouyuCollabUsernameExportItem[];
usernames: string[];
}
export interface DouyuCollabSummary {
total: number;
@@ -301,6 +323,11 @@ export const douyuCollabApi = {
api.post<DouyuCollabCkExportResult, DouyuCollabCkExportResult>(
`/douyu/external-orders/${id}/ck/copy`,
),
exportUsernames: (ids: number[]) =>
api.post<DouyuCollabUsernameExportResult, DouyuCollabUsernameExportResult>(
'/douyu/external-orders/usernames/export',
{ ids },
),
getOrder: (id: number) =>
api.get<DouyuCollabOrder, DouyuCollabOrder>(`/douyu/external-orders/${id}`),
updateOrder: (id: number, data: DouyuCollabOrderUpdate) =>
+114 -1
View File
@@ -43,6 +43,7 @@ import {
SyncOutlined,
TagOutlined,
UndoOutlined,
UserOutlined,
} from '@ant-design/icons';
import HuyaCollabHomeImageUploader from '../components/HuyaCollabHomeImageUploader';
import DouyuCollabShopModal from '../components/DouyuCollabShopModal';
@@ -58,6 +59,7 @@ import type {
DouyuCollabStatus,
DouyuCollabSummary,
DouyuCollabTimeline,
DouyuCollabUsernameExportResult,
} from '../api/douyuCollab';
import { usePermissions } from '../hooks/usePermissions';
import { useCollabRealtime, type CollabRealtimeEvent } from '../hooks/useCollabRealtime';
@@ -258,6 +260,12 @@ export default function DouyuCollabPage() {
const [ckExport, setCkExport] = useState<DouyuCollabCkExportResult | null>(null);
const [ckExportFormat, setCkExportFormat] = useState<'plain' | 'with_order'>('plain');
// 批量导出斗鱼用户名:只读列表数据,不含 CK
const [usernameExportOpen, setUsernameExportOpen] = useState(false);
const [usernameExporting, setUsernameExporting] = useState(false);
const [usernameExport, setUsernameExport] =
useState<DouyuCollabUsernameExportResult | null>(null);
const loadOrders = useCallback(async (options?: { silent?: boolean }) => {
const silent = options?.silent === true;
if (!silent) setLoading(true);
@@ -337,7 +345,8 @@ export default function DouyuCollabPage() {
shopModalOpen ||
batchStatusOpen ||
statusTarget ||
ckExportOpen,
ckExportOpen ||
usernameExportOpen,
);
// 暂停后连 WebSocket 也不建:既不自动重拉,也不做断线兜底轮询。
const realtimeSyncEnabled = canView && !realtimePaused;
@@ -440,6 +449,11 @@ export default function DouyuCollabPage() {
[ckExport],
);
const usernameExportText = useMemo(
() => (usernameExport?.usernames ?? []).join('\n'),
[usernameExport],
);
const refreshAll = () => {
void loadOrders();
@@ -796,6 +810,38 @@ export default function DouyuCollabPage() {
}
};
const openUsernameExport = async () => {
if (selectedIds.length === 0) {
message.warning('请先勾选协作单');
return;
}
setUsernameExporting(true);
try {
const result = await douyuCollabApi.exportUsernames(selectedIds);
setUsernameExport(result);
setUsernameExportOpen(true);
} catch (error) {
message.error(getErrorMessage(error));
} finally {
setUsernameExporting(false);
}
};
const closeUsernameExport = () => {
setUsernameExportOpen(false);
setUsernameExport(null);
};
const copyUsernameExport = async () => {
if (!usernameExport?.unique) return;
try {
await copyTextToClipboard(usernameExportText);
message.success(`已复制 ${usernameExport.unique} 个斗鱼用户名到剪贴板`);
} catch (error) {
message.error(`复制失败:${getErrorMessage(error)}(可以在下面的框里手动选中复制)`);
}
};
const archiveOrders = async (order: DouyuCollabOrder) => {
try {
const result = await douyuCollabApi.archive([order.id], '');
@@ -1421,6 +1467,18 @@ export default function DouyuCollabPage() {
</Button>
{realtimePending && <Tag color="processing">有更新待同步</Tag>}
<Text type="secondary">已选 {selectedCount} 单</Text>
{canView && (
<Tooltip title="导出勾选订单关联的斗鱼用户名,一行一个并自动去重">
<Button
icon={<UserOutlined />}
disabled={selectedCount === 0 || loading || usernameExporting}
loading={usernameExporting}
onClick={() => void openUsernameExport()}
>
批量导出用户名{selectedCount > 0 ? `(${selectedCount})` : ''}
</Button>
</Tooltip>
)}
{canExportCk && archiveScope !== 'archived' && (
<Tooltip title="勾选几行就复制几份关联账号的 CK;明文只在弹窗里出现">
<Button
@@ -1740,6 +1798,61 @@ export default function DouyuCollabPage() {
</Space>
</Modal>
<Modal
title={`导出斗鱼用户名(勾选 ${usernameExport?.total ?? 0} 条,共 ${usernameExport?.unique ?? 0} 个)`}
open={usernameExportOpen}
onCancel={closeUsernameExport}
width={680}
footer={[
<Button key="close" onClick={closeUsernameExport}>
关闭
</Button>,
<Button
key="copy"
type="primary"
icon={<CopyOutlined />}
disabled={!usernameExport?.unique}
onClick={() => void copyUsernameExport()}
>
复制到剪贴板
</Button>,
]}
destroyOnHidden
>
<Space direction="vertical" size={8} style={{ width: '100%' }}>
<Alert
type="info"
showIcon
message="只导出斗鱼用户名,不包含 CK"
description="每行一个用户名,同名账号会自动去重;关闭弹窗后内容即清空。"
/>
{usernameExport && usernameExport.skipped.length > 0 && (
<Text type="warning" style={{ fontSize: 12 }}>
跳过 {usernameExport.skipped.length} 条:
{usernameExport.skipped
.map((item) =>
item.order_no ? `${item.order_no}(${item.reason})` : item.reason,
)
.join(';')}
</Text>
)}
<Input.TextArea
rows={10}
value={usernameExportText}
readOnly
placeholder="没有可导出的斗鱼用户名"
style={{
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
fontSize: 12,
}}
/>
<Text type="secondary" style={{ fontSize: 12 }}>
共 {usernameExport?.unique ?? 0} 个用户名 · {usernameExportText.length} 字符;
按勾选订单导出,重复用户名只保留一次。
</Text>
</Space>
</Modal>
<Modal
title={`导出 CK(勾选 ${ckExport?.total ?? 0} 条,可导出 ${ckExport?.exported ?? 0} 条)`}
open={ckExportOpen}