// EwoooC - 商品看板(Nothing × Claude 美學:安靜、結構化、Mono 為主) // ===== 編號標籤(呼應 sidebar 的 01/02/03) ===== const SectionLabel = ({ num, children, sub }) => (
{num}
); // ===== KPI 大數字(6 顆並排,對齊正式環境) ===== const KPIRow = ({ stats, dynamics, onAiClick }) => { const items = [ { label: '比價覆蓋率', value: '31.5%', sub: `${(2121).toLocaleString()} / ${stats.total.toLocaleString()} ACTIVE`, tone: 'caramel' }, { label: 'PChome 領先', value: 784, sub: '平均壓低 +12.0%', tone: 'ink', accent: true }, { label: 'MOMO 領先', value: 952, sub: 'MOMO 價格低於 PChome' }, { label: 'AI 挑品', value: 50, sub: '查看 50 項清單', tone: 'honey', interactive: true, action: 'ai' }, { label: '待比對', value: '4,615', sub: '高優先級盡快比對' }, { label: '資料新鮮度', value: '已更新', sub: `2026-05-01 06:52`, tone: 'success' }, ]; const colorMap = { caramel: 'var(--momo-warm-caramel)', honey: 'var(--momo-warm-honey)', rust: 'var(--momo-warm-rust)', success: 'var(--momo-success)', }; return (
{items.map((it, i) => (
onAiClick && onAiClick() : undefined} role={it.interactive ? 'button' : undefined} tabIndex={it.interactive ? 0 : undefined} style={{ padding: '18px 20px', borderRight: i < items.length - 1 ? '1px solid var(--momo-border-light)' : 'none', background: it.accent ? 'var(--momo-ink)' : 'transparent', color: it.accent ? '#faf7f0' : 'inherit', position: 'relative', overflow: 'hidden', cursor: it.interactive ? 'pointer' : 'default', transition: 'var(--momo-transition-base)', }} onMouseEnter={it.interactive ? (e => e.currentTarget.style.background = it.accent ? 'var(--momo-ink-soft)' : 'var(--momo-bg-paper)') : undefined} onMouseLeave={it.interactive ? (e => e.currentTarget.style.background = it.accent ? 'var(--momo-ink)' : 'transparent') : undefined}> {/* 點陣背景:深色卡濃一點,淺色卡淡一點當紋理 */}
{it.label}
{typeof it.value === 'number' ? it.value.toLocaleString() : it.value}
{it.sub}{it.interactive && ( )}
))}
); }; // ===== 焦點 + 排程(雙欄,安靜版) ===== const FocusRow = ({ dynamics, schedule, stats }) => (
{/* 最活躍分類 */}
最活躍分類
{dynamics.hottestCategory}
{dynamics.hottestCount} 件商品變動
{/* 最大變動 */}
最大變動
+${dynamics.biggestChange.amount.toLocaleString()}
{dynamics.biggestChange.product}
{/* 爬蟲排程 */}
爬蟲排程 ACTIVE
{schedule.lastRun}
掃描 {schedule.scanned.toLocaleString()} 筆 · 新增 +{schedule.added}
); // ===== 篩選列(簡潔版) ===== const FilterBar = ({ search, setSearch, category, setCategory, tab, setTab }) => { const tabs = [ { id: 'all', label: '全部' }, { id: 'ai', label: 'AI 挑品' }, { id: 'new', label: '新上架' }, { id: 'up', label: '漲價' }, { id: 'down', label: '降價' }, { id: 'off', label: '下架' }, ]; return (
setSearch(e.target.value)} size="sm" />
{/* segmented tabs */}
{tabs.map(t => { const active = tab === t.id; return ( ); })}
); }; // ===== 漲跌格子 ===== const ChangeCell = ({ value }) => { if (value == null) return ; if (value === 0) return 0; const up = value > 0; return ( {up ? '▲' : '▼'} {up ? '+' : ''}{value.toLocaleString()} ); }; // ===== Helper:模擬 PChome 價格與警報判斷(接真資料前的 placeholder) ===== const mockPchomePrice = (p) => { const seed = parseInt(String(p.id).slice(-3), 10) || 0; const offset = ((seed % 11) - 5) * 0.04; // ±20% 區間 const v = Math.round(p.price * (1 + offset)); if (seed % 7 === 0) return null; // 部分商品標「待比對」 return v; }; const judgeAlert = (momo, pchome) => { if (pchome == null) return { tone: 'muted', label: '待比對', sub: '無 PChome 資料' }; const diff = momo - pchome; const pct = (diff / pchome) * 100; if (Math.abs(pct) < 1) return { tone: 'earth', label: '價格相近', sub: `差 $${Math.abs(diff)}` }; if (pct < 0) return { tone: 'caramel', label: 'MOMO 較低', sub: `領先 $${Math.abs(diff)} (${pct.toFixed(1)}%)` }; return { tone: 'rust', label: 'MOMO 偏高', sub: `落後 $${diff} (+${pct.toFixed(1)}%)` }; }; // ===== 雙平台價格對比卡(業界主流:主價大 + 對比價小一階 + 差額標示) ===== const PriceCompareCell = ({ momo, pchome }) => { const lower = pchome != null && pchome < momo; const higher = pchome != null && pchome > momo; return (
{/* MOMO 主價 */}
MOMO ${momo.toLocaleString()}
{/* PChome 對比價 */}
PChome {pchome != null ? ( ${pchome.toLocaleString()} ) : ( )}
); }; const ProductTable = ({ products, total, schedule, onRowClick }) => { const PER_PAGE = 50; const [page, setPage] = React.useState(1); React.useEffect(() => { setPage(1); }, [products.length]); const totalPages = Math.max(1, Math.ceil(products.length / PER_PAGE)); const visible = products.slice((page - 1) * PER_PAGE, page * PER_PAGE); return (
04 商品列表 {total.toLocaleString()} 筆
排程 {schedule.lastRun} · 掃描 {schedule.scanned.toLocaleString()} · 新增 +{schedule.added}
{[ { label: '分類', w: 120 }, { label: '商品名稱' }, { label: '雙平台價格', w: 180, align: 'right' }, { label: '警報判斷', w: 150 }, { label: '昨日', w: 90, align: 'right' }, { label: '本週', w: 90, align: 'right' }, { label: '更新時間', w: 110, align: 'right' }, ].map((h, i) => ( ))} {visible.map((p, idx) => { const pchome = mockPchomePrice(p); const alert = judgeAlert(p.price, pchome); return ( onRowClick && onRowClick(p)} style={{ borderTop: idx === 0 ? 'none' : '1px solid var(--momo-border-light)', cursor: 'pointer', transition: 'var(--momo-transition-base)', boxShadow: 'inset 0 0 0 0 var(--momo-warm-caramel)', }} onMouseEnter={e => { e.currentTarget.style.background = 'var(--momo-bg-paper)'; e.currentTarget.style.boxShadow = 'inset 3px 0 0 0 var(--momo-warm-caramel)'; }} onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.boxShadow = 'inset 0 0 0 0 var(--momo-warm-caramel)'; }}> ); })}
{h.label}
{p.category}
{p.emoji}
{p.name}
ID · {p.id}
{alert.label} {alert.sub}
{p.updatedAt}
{/* 分頁器 */} {products.length > 0 && (
{((page - 1) * PER_PAGE + 1).toLocaleString()}–{Math.min(page * PER_PAGE, products.length).toLocaleString()} · 共 {products.length.toLocaleString()} 筆
{page} / {totalPages}
)}
); }; // ===== 比價決策焦點(3 欄商品卡片) ===== const FocusCard = ({ title, badge, products, accentTone = 'caramel' }) => { const accentColor = { caramel: 'var(--momo-warm-caramel)', rust: 'var(--momo-warm-rust)', honey: 'var(--momo-warm-honey)', }[accentTone] || 'var(--momo-warm-caramel)'; return (
{/* 左上 accent 條 */}