feat(marquee): 首页轮播种子预览改内联常驻、随数据源模式实时刷新
系统配置「首页轮播种子」页:预览从点按钮弹窗改为常驻在「数据源」开关下方, 切换「混播/只真实/只种子」即时重拉、所见即所得。 - 预览请求带上当前选中的 mode(配套 app-server 预览接口的 mode 入参),不依赖 PATCH 落库时序;拨动开关(乐观更新)即刻预览对应模式,切换失败回滚预览也随之回滚。 - 移除原「预览效果」弹窗及相关状态;「只真实」无数据时给空态提示。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,7 @@ interface PreviewItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const yuan = (cents: number) => (cents / 100).toFixed(2);
|
const yuan = (cents: number) => (cents / 100).toFixed(2);
|
||||||
|
const MODE_LABEL: Record<string, string> = { mixed: '混播', real: '只真实', seed: '只种子' };
|
||||||
|
|
||||||
/** 首页轮播「种子」管理(真实记录不足时的兜底假数据,现为「生成规则」)。「系统配置 / 首页」里的一个区块。 */
|
/** 首页轮播「种子」管理(真实记录不足时的兜底假数据,现为「生成规则」)。「系统配置 / 首页」里的一个区块。 */
|
||||||
export default function HomeMarqueeSeeds() {
|
export default function HomeMarqueeSeeds() {
|
||||||
@@ -66,7 +67,6 @@ export default function HomeMarqueeSeeds() {
|
|||||||
const [bulkOpen, setBulkOpen] = useState(false);
|
const [bulkOpen, setBulkOpen] = useState(false);
|
||||||
const [bulkForm] = Form.useForm<BulkVals>();
|
const [bulkForm] = Form.useForm<BulkVals>();
|
||||||
|
|
||||||
const [previewOpen, setPreviewOpen] = useState(false);
|
|
||||||
const [previewItems, setPreviewItems] = useState<PreviewItem[]>([]);
|
const [previewItems, setPreviewItems] = useState<PreviewItem[]>([]);
|
||||||
const [previewLoading, setPreviewLoading] = useState(false);
|
const [previewLoading, setPreviewLoading] = useState(false);
|
||||||
|
|
||||||
@@ -168,11 +168,15 @@ export default function HomeMarqueeSeeds() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== 预览实际 feed =====
|
// ===== 预览实际 feed(常驻内联,跟随数据源模式实时刷新)=====
|
||||||
const fetchPreview = async () => {
|
// 显式把当前选中的 mode 传给后端预览:不依赖 PATCH 落库时序 —— 拨动开关(乐观更新 mode)即刻预览
|
||||||
|
// 对应模式;切换失败回滚 mode 时预览也随之回滚。种子随机抽样,故也可点「换一批」重随机。
|
||||||
|
const fetchPreview = async (m: string = mode) => {
|
||||||
setPreviewLoading(true);
|
setPreviewLoading(true);
|
||||||
try {
|
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);
|
setPreviewItems(data.items);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
message.error(errMsg(e));
|
message.error(errMsg(e));
|
||||||
@@ -180,10 +184,11 @@ export default function HomeMarqueeSeeds() {
|
|||||||
setPreviewLoading(false);
|
setPreviewLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const openPreview = () => {
|
// 模式变化(初次拉到持久化模式 / 拨动开关 / 切换失败回滚)→ 重拉预览,保证「所见=当前模式」。
|
||||||
setPreviewOpen(true);
|
useEffect(() => {
|
||||||
fetchPreview();
|
fetchPreview(mode);
|
||||||
};
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [mode]);
|
||||||
|
|
||||||
const toggle = async (s: Seed, enabled: boolean) => {
|
const toggle = async (s: Seed, enabled: boolean) => {
|
||||||
try {
|
try {
|
||||||
@@ -300,6 +305,66 @@ export default function HomeMarqueeSeeds() {
|
|||||||
混播=真实优先+种子补位;只真实=不掺假数据(真实不足则少显示);只种子=只用下方种子/合成(演示用)
|
混播=真实优先+种子补位;只真实=不掺假数据(真实不足则少显示);只种子=只用下方种子/合成(演示用)
|
||||||
</span>
|
</span>
|
||||||
</Space>
|
</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>
|
<Space style={{ marginBottom: 12 }} wrap>
|
||||||
<Button type="primary" size="small" onClick={openAdd}>
|
<Button type="primary" size="small" onClick={openAdd}>
|
||||||
新增种子
|
新增种子
|
||||||
@@ -307,9 +372,6 @@ export default function HomeMarqueeSeeds() {
|
|||||||
<Button size="small" onClick={openBulk}>
|
<Button size="small" onClick={openBulk}>
|
||||||
批量生成
|
批量生成
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="small" onClick={openPreview}>
|
|
||||||
预览效果
|
|
||||||
</Button>
|
|
||||||
<span style={{ borderLeft: '1px solid #eee', height: 18 }} />
|
<span style={{ borderLeft: '1px solid #eee', height: 18 }} />
|
||||||
<Button size="small" disabled={!selectedRowKeys.length} onClick={() => batchSetEnabled(true)}>
|
<Button size="small" disabled={!selectedRowKeys.length} onClick={() => batchSetEnabled(true)}>
|
||||||
批量启用
|
批量启用
|
||||||
@@ -413,48 +475,6 @@ export default function HomeMarqueeSeeds() {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user