feat(weibo): 增加协作 CK 批量复制与商品图片代理
This commit is contained in:
@@ -22,6 +22,7 @@ export type {
|
||||
WeiboCollabArchiveResult,
|
||||
WeiboCollabArchiveScope,
|
||||
WeiboCollabBalanceCheckResult,
|
||||
WeiboCollabCkExportResult,
|
||||
WeiboCollabExchangeCheckResult,
|
||||
WeiboCollabGoods,
|
||||
WeiboCollabLiveGoods,
|
||||
|
||||
@@ -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[]>(
|
||||
|
||||
@@ -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 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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' && (
|
||||
|
||||
Reference in New Issue
Block a user