feat(marquee): 首页轮播种子预览改内联常驻、随数据源模式实时刷新 #40

Merged
marco merged 2 commits from feat/marquee-preview-inline-live into main 2026-07-08 00:57:12 +08:00
Showing only changes of commit f950bcd841 - Show all commits
+73 -53
View File
@@ -49,6 +49,7 @@ interface PreviewItem {
}
const yuan = (cents: number) => (cents / 100).toFixed(2);
const MODE_LABEL: Record<string, string> = { mixed: '混播', real: '只真实', seed: '只种子' };
/** 首页轮播「种子」管理(真实记录不足时的兜底假数据,现为「生成规则」)。「系统配置 / 首页」里的一个区块。 */
export default function HomeMarqueeSeeds() {
@@ -66,7 +67,6 @@ export default function HomeMarqueeSeeds() {
const [bulkOpen, setBulkOpen] = useState(false);
const [bulkForm] = Form.useForm<BulkVals>();
const [previewOpen, setPreviewOpen] = useState(false);
const [previewItems, setPreviewItems] = useState<PreviewItem[]>([]);
const [previewLoading, setPreviewLoading] = useState(false);
@@ -168,11 +168,15 @@ export default function HomeMarqueeSeeds() {
}
};
// ===== 预览实际 feed =====
const fetchPreview = async () => {
// ===== 预览实际 feed(常驻内联,跟随数据源模式实时刷新)=====
// 显式把当前选中的 mode 传给后端预览:不依赖 PATCH 落库时序 —— 拨动开关(乐观更新 mode)即刻预览
// 对应模式;切换失败回滚 mode 时预览也随之回滚。种子随机抽样,故也可点「换一批」重随机。
const fetchPreview = async (m: string = mode) => {
setPreviewLoading(true);
try {
const { data } = await api.get<{ items: PreviewItem[] }>('/admin/api/marquee-seeds/preview?limit=8');
const { data } = await api.get<{ items: PreviewItem[] }>('/admin/api/marquee-seeds/preview', {
params: { limit: 8, mode: m },
});
setPreviewItems(data.items);
} catch (e) {
message.error(errMsg(e));
@@ -180,10 +184,11 @@ export default function HomeMarqueeSeeds() {
setPreviewLoading(false);
}
};
const openPreview = () => {
setPreviewOpen(true);
fetchPreview();
};
// 模式变化(初次拉到持久化模式 / 拨动开关 / 切换失败回滚)→ 重拉预览,保证「所见=当前模式」。
useEffect(() => {
fetchPreview(mode);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mode]);
const toggle = async (s: Seed, enabled: boolean) => {
try {
@@ -300,6 +305,66 @@ export default function HomeMarqueeSeeds() {
=+;=();=/()
</span>
</Space>
{/* 预览:常驻内联,跟随上方「数据源」模式实时刷新(所见=客户端实际效果)。 */}
<div
style={{
marginBottom: 16,
border: '1px solid #f0f0f0',
borderRadius: 8,
padding: '10px 12px',
background: '#fafafa',
}}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 6,
}}
>
<span style={{ fontSize: 13, fontWeight: 500 }}>
· {MODE_LABEL[mode] ?? mode}
<span style={{ color: '#999', fontWeight: 400, fontSize: 12, marginLeft: 8 }}>
,;/
</span>
</span>
<Button size="small" onClick={() => fetchPreview()}>
</Button>
</div>
<Spin spinning={previewLoading}>
<div style={{ minHeight: 120 }}>
{previewItems.length === 0 && !previewLoading ? (
<div style={{ color: '#bbb', textAlign: 'center', padding: '36px 0', fontSize: 13 }}>
{mode === 'real'
? '当前无真实比价记录 ——「只真实」模式不补种子,故为空'
: '暂无数据'}
</div>
) : (
previewItems.map((it, i) => (
<div
key={i}
style={{
display: 'flex',
justifyContent: 'space-between',
padding: '6px 4px',
borderBottom: '1px solid #f0f0f0',
}}
>
<span>
{it.masked_user} {' '}
<b style={{ color: '#fa541c' }}>{yuan(it.saved_amount_cents)} </b>
</span>
<span style={{ color: '#999' }}>{it.time}</span>
</div>
))
)}
</div>
</Spin>
</div>
<Space style={{ marginBottom: 12 }} wrap>
<Button type="primary" size="small" onClick={openAdd}>
@@ -307,9 +372,6 @@ export default function HomeMarqueeSeeds() {
<Button size="small" onClick={openBulk}>
</Button>
<Button size="small" onClick={openPreview}>
</Button>
<span style={{ borderLeft: '1px solid #eee', height: 18 }} />
<Button size="small" disabled={!selectedRowKeys.length} onClick={() => batchSetEnabled(true)}>
@@ -413,48 +475,6 @@ export default function HomeMarqueeSeeds() {
</Form.Item>
</Form>
</Modal>
{/* 预览实际 feed */}
<Modal
title="预览轮播效果"
open={previewOpen}
onCancel={() => setPreviewOpen(false)}
footer={
<Space>
<Button size="small" onClick={fetchPreview}>
</Button>
<Button size="small" type="primary" onClick={() => setPreviewOpen(false)}>
</Button>
</Space>
}
>
<p style={{ color: '#999', marginTop: 0 }}>
客户端实际会看到的混播结果:真实记录会插队,/
</p>
<Spin spinning={previewLoading}>
<div style={{ minHeight: 120 }}>
{previewItems.map((it, i) => (
<div
key={i}
style={{
display: 'flex',
justifyContent: 'space-between',
padding: '7px 4px',
borderBottom: '1px solid #f0f0f0',
}}
>
<span>
{it.masked_user} {' '}
<b style={{ color: '#fa541c' }}>{yuan(it.saved_amount_cents)} </b>
</span>
<span style={{ color: '#999' }}>{it.time}</span>
</div>
))}
</div>
</Spin>
</Modal>
</div>
);
}