import { useEffect, useRef, useState } from 'react'; import type { ActivityPosterItem, PartnerAssocSummary } from '@dukang/shared-types'; import { request } from '../lib/api'; import { copyActivityCopy, downloadBlob, fetchActivityPosterImage, fetchAssocQrcodeImage, getActivityPosterSelection, listActivityPosters, saveActivityPosterSelection, } from '../lib/activity-posters'; import { toastError, toastSuccess } from '../lib/toast'; import { blobToDataUrl, isHttpImageUrl, previewSaveableImage } from '../lib/wechat-save-image'; import { isWechatEnv } from '../lib/weixin'; const NONE_ID = 'none'; type Props = { initialId?: string | null; }; export default function ActivityPosterPicker({ initialId }: Props) { const [items, setItems] = useState([]); const [qrcodeUrl, setQrcodeUrl] = useState(null); const [partnerId, setPartnerId] = useState(''); const [selectedId, setSelectedId] = useState(NONE_ID); const [previewUrls, setPreviewUrls] = useState>({}); const [listLoading, setListLoading] = useState(true); const [previewLoading, setPreviewLoading] = useState(false); const [downloading, setDownloading] = useState(false); const blobsRef = useRef(new Map()); const urlsRef = useRef([]); useEffect(() => () => { urlsRef.current.forEach((url) => URL.revokeObjectURL(url)); }, []); useEffect(() => { let cancelled = false; Promise.all([ listActivityPosters(), request('PARTNER_H5', '/partner/assoc', { silent: true }), getActivityPosterSelection().catch(() => ({ posterId: null })), ]) .then(([list, assoc, selection]) => { if (cancelled) return; setItems(list); setQrcodeUrl(assoc.qrcodeUrl); setPartnerId(assoc.partnerId); const saved = selection.posterId && list.some((item) => item.id === selection.posterId) ? selection.posterId : null; const fromQuery = initialId && list.some((item) => item.id === initialId) ? initialId : null; const next = saved || fromQuery || NONE_ID; setSelectedId(next); if (!saved && fromQuery) { void saveActivityPosterSelection(fromQuery).catch((err) => { toastError(err instanceof Error ? err.message : '保存失败'); }); } }) .catch((e) => toastError(e instanceof Error ? e.message : '加载活动图失败')) .finally(() => { if (!cancelled) setListLoading(false); }); return () => { cancelled = true; }; }, [initialId]); useEffect(() => { if (selectedId === NONE_ID || previewUrls[selectedId] || blobsRef.current.has(selectedId)) return; let cancelled = false; setPreviewLoading(true); fetchActivityPosterImage(selectedId) .then((blob) => { if (cancelled) return; blobsRef.current.set(selectedId, blob); const url = URL.createObjectURL(blob); urlsRef.current.push(url); setPreviewUrls((prev) => ({ ...prev, [selectedId]: url })); }) .catch((e) => { if (!cancelled) toastError(e instanceof Error ? e.message : '预览失败'); }) .finally(() => { if (!cancelled) setPreviewLoading(false); }); return () => { cancelled = true; }; }, [selectedId, previewUrls]); const selected = items.find((item) => item.id === selectedId) ?? null; const previewUrl = selectedId !== NONE_ID ? previewUrls[selectedId] : undefined; async function downloadSelected() { setDownloading(true); try { if (selectedId === NONE_ID) { if (isWechatEnv() && isHttpImageUrl(qrcodeUrl)) { await previewSaveableImage(qrcodeUrl); return; } const blob = await fetchAssocQrcodeImage(); if (isWechatEnv()) { setQrcodeUrl(await blobToDataUrl(blob)); toastSuccess('请长按上方图片保存到相册'); return; } downloadBlob(blob, `partner-assoc-${partnerId || 'qr'}.png`); toastSuccess('已开始下载'); return; } if (!selected) return; let blob = blobsRef.current.get(selected.id); if (!blob) { blob = await fetchActivityPosterImage(selected.id); blobsRef.current.set(selected.id, blob); } if (isWechatEnv()) { let url = previewUrls[selected.id]; if (!url || url.startsWith('blob:')) { url = await blobToDataUrl(blob); setPreviewUrls((prev) => ({ ...prev, [selected.id]: url })); } toastSuccess('请长按上方图片保存到相册'); return; } downloadBlob(blob, `activity-poster-${selected.id}.png`); toastSuccess('已开始下载'); } catch (e) { toastError(e instanceof Error ? e.message : '下载失败'); } finally { setDownloading(false); } } async function selectPoster(id: string) { const prev = selectedId; setSelectedId(id); try { await saveActivityPosterSelection(id === NONE_ID ? null : id); } catch (e) { setSelectedId(prev); toastError(e instanceof Error ? e.message : '保存失败'); } } function renderRadioRow(id: string, label: string) { return ( ); } if (listLoading) { return
加载活动图…
; } return (
{renderRadioRow(NONE_ID, '无')} {selectedId === NONE_ID && (
{qrcodeUrl ? (
关联码
) : (

关联码尚未生成

)} {isWechatEnv() && (

微信内请长按上方图片保存

)}
)}
{items.map((item) => (
{renderRadioRow(item.id, item.title)} {selectedId === item.id && (
{item.title} {previewLoading && !previewUrl && (
正在贴入二维码…
)}
{item.copyText ? (

{item.copyText}

) : (

暂无推广文案

)}
{previewUrl && isWechatEnv() && (

微信内请长按上方图片保存

)}
)}
))}
); }