feat(weibo): 增加协作 CK 批量复制与商品图片代理

This commit is contained in:
yml
2026-11-12 14:12:50 +08:00
parent 31045e0f2e
commit 0e29717c97
18 changed files with 604 additions and 153 deletions
+1
View File
@@ -22,6 +22,7 @@ export type {
WeiboCollabArchiveResult,
WeiboCollabArchiveScope,
WeiboCollabBalanceCheckResult,
WeiboCollabCkExportResult,
WeiboCollabExchangeCheckResult,
WeiboCollabGoods,
WeiboCollabLiveGoods,
+22
View File
@@ -155,6 +155,23 @@ export interface WeiboCollabOrderList {
page_size: number;
}
export interface WeiboCollabCkExportItem {
id: number;
order_no: string;
account_id: number;
uid: string;
nickname: string;
ck_status: string;
ck_text: string;
}
export interface WeiboCollabCkExportResult {
total: number;
exported: number;
skipped: Array<{ id: number; order_no: string; reason: string }>;
items: WeiboCollabCkExportItem[];
}
export interface WeiboCollabSummary {
total: number;
by_status: Record<string, number>;
@@ -371,6 +388,11 @@ export const weiboCollabApi = {
'/weibo/external-orders/unarchive',
ids,
),
exportCks: (ids: number[]) =>
api.post<WeiboCollabCkExportResult, WeiboCollabCkExportResult>(
'/weibo/external-orders/ck/export',
{ ids },
),
deleteOrder: (id: number) => api.delete(`/weibo/external-orders/${id}`),
metrics: (id: number) =>
api.get<WeiboCollabMetric[], WeiboCollabMetric[]>(
+10 -1
View File
@@ -97,6 +97,14 @@ const WEIBO_BATCH_RECHARGE_MAX = 20;
const WEIBO_BATCH_RECHARGE_CONCURRENCY = 3;
const WEIBO_BATCH_RECHARGE_POLL_INTERVAL_MS = 8_000;
function normalizeImageURL(value: string): string {
const imageURL = value.trim();
if (!imageURL || imageURL.startsWith('data:image/')) return imageURL;
if (imageURL.startsWith('//')) return `https:${imageURL}`;
if (/^http:\/\//i.test(imageURL)) return imageURL.replace(/^http:/i, 'https:');
return imageURL;
}
type BatchRechargePhase = 'queued' | 'creating' | 'ready' | 'error';
type BatchCreditPhase = '' | 'checking' | 'pending' | 'credited' | 'error';
@@ -1492,8 +1500,9 @@ export default function WeiboAccountsPage({
<Space align="start">
{goods.goods_image && (
<img
src={goods.goods_image}
src={normalizeImageURL(goods.goods_image)}
alt=""
referrerPolicy="no-referrer"
style={{ width: 42, height: 42, objectFit: 'cover', borderRadius: 4 }}
/>
)}
+51 -21
View File
@@ -133,6 +133,7 @@ export default function WeiboCollabPage() {
const navigate = useNavigate();
const canEdit = can('weibo:collab:edit');
const canManage = can('weibo:collab:manage');
const canExportCk = can('weibo:account:ck_export');
const [orders, setOrders] = useState<WeiboCollabOrder[]>([]);
const [summary, setSummary] = useState<WeiboCollabSummary | null>(null);
@@ -145,6 +146,7 @@ export default function WeiboCollabPage() {
const [pageSize, setPageSize] = useState(20);
const [loading, setLoading] = useState(false);
const [selectedIds, setSelectedIds] = useState<number[]>([]);
const [copyingCk, setCopyingCk] = useState(false);
const [keyword, setKeyword] = useState('');
const [status, setStatus] = useState('');
const [shop, setShop] = useState('');
@@ -615,38 +617,63 @@ export default function WeiboCollabPage() {
[summary],
);
const copySelectedAccountValues = async (kind: 'username' | 'uid') => {
const copySelectedUsernames = async () => {
const selectedOrders = orders.filter((row) => selectedIds.includes(row.id));
const values = Array.from(
new Set(
selectedOrders
.map((row) =>
kind === 'uid'
? row.account_uid
: row.account_nickname || row.account_uid,
)
.map((row) => row.account_nickname || row.account_uid)
.map((value) => String(value || '').trim())
.filter(Boolean),
),
);
if (values.length === 0) {
message.warning(
kind === 'uid' ? '所选订单没有微博 UID' : '所选订单没有微博用户名',
);
message.warning('所选订单没有微博用户名');
return;
}
try {
await copyTextToClipboard(values.join('\n'));
message.success(
`已复制 ${values.length} 个${
kind === 'uid' ? ' UID' : '用户名'
},可直接粘贴到工作台导入搜索框`,
);
message.success(`已复制 ${values.length} 个用户名`);
} catch (error) {
message.error(getErrorMessage(error));
}
};
const copySelectedCks = async () => {
setCopyingCk(true);
try {
const result = await weiboCollabApi.exportCks(selectedIds);
const items = Array.from(
new Map(
result.items.map((item) => [item.account_id, item]),
).values(),
);
const failures = result.skipped.map(
(item) => `${item.order_no || `单 ${item.id}`}:${item.reason}`,
);
if (items.length > 0) {
await copyTextToClipboard(
items.map((item) => item.ck_text.trim()).join('\n'),
);
if (failures.length > 0) {
message.warning(
`已复制 ${items.length} 份 CK;${failures.length} 单未能导出:${failures
.slice(0, 3)
.join(';')}`,
);
} else {
message.success(`已复制 ${items.length} 份 CK`);
}
} else {
message.error(`没有可复制的 CK:${failures.join(';')}`);
}
} catch (error) {
message.error(getErrorMessage(error));
} finally {
setCopyingCk(false);
}
};
const columns: TableProps<WeiboCollabOrder>['columns'] = [
{
title: '序号',
@@ -997,16 +1024,19 @@ export default function WeiboCollabPage() {
<>
<Button
icon={<CopyOutlined />}
onClick={() => void copySelectedAccountValues('username')}
onClick={() => void copySelectedUsernames()}
>
复制所选用户名
</Button>
<Button
icon={<CopyOutlined />}
onClick={() => void copySelectedAccountValues('uid')}
>
复制所选 UID
</Button>
{canExportCk && (
<Button
icon={<CopyOutlined />}
loading={copyingCk}
onClick={() => void copySelectedCks()}
>
复制所选 CK
</Button>
)}
</>
)}
{canEdit && selectedIds.length > 0 && archiveScope !== 'archived' && (