!DOCTYPE html html lang=zh-TW head meta charset=UTF-8 meta name=viewport content=width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no title購物狂的專屬祕書 - Pink Strawberry Y2Ktitle link rel=icon type=imagesvg+xml href=dataimagesvg+xml,%3Csvg xmlns='httpwww.w3.org2000svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='grad' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color%23FFD1DC;stop-opacity1' %3E%3Cstop offset='100%25' style='stop-color%23FF8AAB;stop-opacity1' %3E%3ClinearGradient%3E%3Cdefs%3E%3Cpath d='M50 20 C65 20 85 30 85 55 C85 75 65 95 50 95 C35 95 15 75 15 55 C15 30 35 20 50 20' fill='url(%23grad)'%3E%3Cpath d='M40 25 Q50 5 60 25 L50 20 Z' fill='%2381C784'%3E%3Ccircle cx='35' cy='45' r='1.2' fill='%23AD1457' opacity='0.6'%3E%3Ccircle cx='65' cy='50' r='1.2' fill='%23AD1457' opacity='0.6'%3E%3Csvg%3E link href=httpsfonts.googleapis.comcss2family=Zen+Maru+Gothicwght@900&display=swap rel=stylesheet script src=httpscdn.tailwindcss.comscript script src=httpsunpkg.comreact@18umdreact.production.min.jsscript script src=httpsunpkg.comreact-dom@18umdreact-dom.production.min.jsscript script src=httpsunpkg.com@babelstandalonebabel.min.jsscript style root { --bg #FFFDF2; --primary #D1C4E9; --primary-dark #9575CD; --mint #B2DFDB; --text #4527A0; --strawberry-pink #FFB7C5; } { font-family 'Zen Maru Gothic', sans-serif !important; font-weight 900 !important; -webkit-tap-highlight-color transparent; } body { background-color var(--bg); color var(--text); margin 0; overflow-x hidden; background-image url(dataimagesvg+xml,%3Csvg xmlns='httpwww.w3.org2000svg' width='120' height='120' viewBox='0 0 100 100'%3E%3Cpath d='M50 20 C65 20 85 30 85 55 C85 75 65 95 50 95 C35 95 15 75 15 55 C15 30 35 20 50 20' fill='%23FFB7C5' opacity='0.1'%3E%3Cpath d='M40 25 Q50 5 60 25 L50 20 Z' fill='%23B2DFDB' opacity='0.1'%3E%3Csvg%3E); } .hide-scrollbar-webkit-scrollbar { display none; } .hide-scrollbar { -ms-overflow-style none; scrollbar-width none; } input, select, textarea { border-radius 1.2rem; border 3px solid #EDE7F6; padding 0.7rem 1rem; background white; outline none; transition all 0.3s; color var(--text); } inputfocus { border-color var(--primary-dark); box-shadow 0 0 0 4px rgba(149, 117, 205, 0.1); } .y2k-card { background white; border 3px solid #EDE7F6; border-radius 2rem; box-shadow 8px 8px 0px #EDE7F6; transition all 0.2s; } .y2k-cardactive { transform translate(2px, 2px); box-shadow 4px 4px 0px #EDE7F6; } .status-tag { padding 4px 12px; border-radius 999px; font-size 11px; color white; } .bg-ordered { background #B39DDB; } .bg-shipped { background var(--primary-dark); } .bg-received { background var(--mint); color #00695C; } .modal-overlay { background rgba(255, 253, 242, 0.9); backdrop-filter blur(12px); z-index 4000; position fixed; inset 0; display flex; align-items center; justify-content center; padding 1rem; } .toast { position fixed; bottom 110px; left 50%; transform translateX(-50%); z-index 10000; padding 12px 24px; border-radius 2rem; background var(--text); color white; font-size 14px; box-shadow 0 10px 30px rgba(69, 39, 160, 0.3); } .suggestion-box { position absolute; top 105%; left 0; right 0; background white; border-radius 1.5rem; border 3px solid var(--primary); z-index 5000; max-height 200px; overflow-y auto; } .suggestion-item { padding 12px 16px; border-bottom 1px solid #EDE7F6; cursor pointer; color var(--text); } .active-tab { background var(--primary-dark) !important; color white !important; box-shadow 0 4px 12px rgba(103, 58, 183, 0.3); } .strawberry-float { animation float 3s ease-in-out infinite; } @keyframes float { 0%, 100% { transform translateY(0); } 50% { transform translateY(-8px); } } style head body div id=rootdiv script src=httpswww.gstatic.comfirebasejs11.1.0firebase-app-compat.jsscript script src=httpswww.gstatic.comfirebasejs11.1.0firebase-firestore-compat.jsscript script src=httpswww.gstatic.comfirebasejs11.1.0firebase-auth-compat.jsscript script const firebaseConfig = { apiKey AIzaSyArZI5pK2bRLdicQsE11n_0_lRcX0J7JtE, authDomain lina0750database.firebaseapp.com, projectId lina0750database, storageBucket lina0750database.firebasestorage.app, messagingSenderId 323253664660, appId 1323253664660webd82e7db4d329c112c6ed81 }; firebase.initializeApp(firebaseConfig); window.db = firebase.firestore(); window.auth = firebase.auth(); script script type=textbabel const { useState, useEffect, useMemo, useRef } = React; const appId = livestream-shopping; const STATUS = { ORDERED '已下單', SHIPPED '已發貨', RECEIVED '已收到貨' }; const WAREHOUSES = ['漳州倉', '上海倉', '東莞倉']; const PLATFORMS = ['抖音', '小紅書', '微信', '淘寶', '閒魚', '其他']; const SecretaryIcon = ({ className = w-10 h-10 }) = ( svg viewBox=0 0 100 100 className={className} defs linearGradient id=pinkBerryGrad x1=0% y1=0% x2=100% y2=100% stop offset=0% style={{stopColor #FFD1DC}} stop offset=100% style={{stopColor #FF8AAB}} linearGradient filter id=shadow x=-10% y=-10% width=120% height=120% feGaussianBlur in=SourceAlpha stdDeviation=2 feOffset dx=2 dy=2 result=offsetblur feComponentTransferfeFuncA type=linear slope=0.3feComponentTransfer feMergefeMergeNode feMergeNode in=SourceGraphic feMerge filter defs g filter=url(#shadow) path d=M50 18 C70 18 90 35 90 60 C90 85 70 98 50 98 C30 98 10 85 10 60 C10 35 30 18 50 18 fill=url(#pinkBerryGrad) path d=M35 22 C45 22 50 5 50 5 C50 5 55 22 65 22 C70 15 80 18 80 18 C65 25 55 28 50 28 C45 28 35 25 20 18 C20 18 30 15 35 22 fill=#81C784 circle cx=35 cy=45 r=1.5 fill=#AD1457 opacity=0.6 circle cx=50 cy=50 r=1.5 fill=#AD1457 opacity=0.6 circle cx=65 cy=45 r=1.5 fill=#AD1457 opacity=0.6 circle cx=42 cy=65 r=1.5 fill=#AD1457 opacity=0.6 circle cx=58 cy=65 r=1.5 fill=#AD1457 opacity=0.6 circle cx=50 cy=80 r=1.5 fill=#AD1457 opacity=0.6 path d=M25 55 Q20 45 35 35 stroke=white strokeWidth=2 strokeLinecap=round fill=none opacity=0.5 g svg ); const SvgIcon = ({ name, size = 20, className = }) = { const icons = { x path d=M18 6L6 18M6 6l12 12 , search React.Fragmentcircle cx=11 cy=11 r=8 line x1=21 cy=21 x2=16.65 y2=16.65 React.Fragment, edit path d=M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7M18.5 2.5a2.121 2.121 0 113 3L12 15l-4 1 1-4 9.5-9.5z , trash path d=M3 6h18m-2 0v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6m3 0V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2 , copy path d=M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2M9 2h6v4H9z , check path d=M20 6L9 17l-5-5 , clipboard path d=M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2 }; return svg width={size} height={size} viewBox=0 0 24 24 fill=none stroke=currentColor strokeWidth=3 strokeLinecap=round strokeLinejoin=round className={className}{icons[name]}svg; }; const showToast = (msg) = { const t = document.createElement('div'); t.className = 'toast animate-bounce'; t.innerText = msg; document.body.appendChild(t); setTimeout(() = t.remove(), 2000); }; const copyToClipboard = (text) = { const el = document.createElement('textarea'); el.value = text; document.body.appendChild(el); el.select(); document.execCommand('copy'); document.body.removeChild(el); showToast(複製成功 📋); }; function TrackingModal({ onConfirm, onClose }) { const [no, setNo] = useState(); return ( div className=modal-overlay div className=bg-white p-8 rounded-[2.5rem] w-full max-w-xs border-4 border-[#FFB7C5] shadow-2xl text-center h3 className=text-xl mb-4 text-[#AD1457]📦 填寫運輸單號h3 input autoFocus placeholder=請輸入單號 value={no} onChange={e = setNo(e.target.value)} className=w-full mb-6 text-center text-lg font-mono border-[#FFD1DC] div className=flex flex-col gap-3 button onClick={() = onConfirm(no)} className=w-full py-4 bg-[#FF8AAB] text-white rounded-2xl shadow-lg activescale-95 transition-all確認發貨 ✨button button onClick={onClose} className=text-gray-400 py-2取消button div div div ); } function EZWayModal({ items, onClose }) { const shippedItems = items.filter(i = i.status === STATUS.SHIPPED && i.trackingNo); return ( div className=modal-overlay div className=bg-white p-8 rounded-[3rem] w-full max-w-sm border-4 border-[#FFB7C5] shadow-2xl max-h-[80vh] overflow-y-auto hide-scrollbar div className=flex justify-between items-center mb-6 h2 className=text-2xl text-[#AD1457]EZWay 填寫區 📝h2 button onClick={onClose} className=p-2 bg-gray-50 rounded-full text-gray-400SvgIcon name=x size={16}button div p className=text-xs text-gray-400 mb-6老闆,這裡彙整了所有「已發貨、待收貨」的單號:p div className=space-y-4 {shippedItems.length 0 shippedItems.map(i = ( div key={i.id} className=p-5 bg-[#FFF0F3] rounded-3xl border-2 border-white shadow-sm flex flex-col gap-2 div className=flex justify-between items-start div className=flex flex-col span className=text-[10px] text-[#FF8AAB] opacity-70 font-bold品名樣式span span className=text-sm line-clamp-1{i.styles}span div span className=text-[9px] bg-white50 px-2 py-0.5 rounded-full text-[#FF8AAB]{i.storeName}span div div className=flex justify-between items-center mt-1 pt-2 border-t border-white40 div className=flex flex-col span className=text-[10px] text-[#FF8AAB] opacity-70 font-bold運輸單號span span className=text-lg font-mono text-[#AD1457] tracking-wider{i.trackingNo}span div button onClick={() = copyToClipboard(i.trackingNo)} className=p-3 bg-white text-[#FF8AAB] rounded-2xl shadow-sm activescale-90 transition-all flex items-center gap-2 text-xs font-bold border border-[#FFE0E6] SvgIcon name=clipboard size={14} 複製 button div div )) div className=text-center py-10 opacity-30 text-sm目前沒有發貨中的寶貝唷div} div button onClick={onClose} className=w-full py-4 mt-8 bg-[#9575CD] text-white rounded-2xl shadow-lg activescale-95 transition-all辛苦了老闆 🍵button div div ); } function AddEditModal({ onClose, onSave, editingItem, items }) { const [form, setForm] = useState(editingItem { platform '抖音', storeName '', styles '', orderDate new Date().toISOString().split('T')[0], trackingNo '', warehouse '漳州倉', status STATUS.ORDERED, price '', images [], note '' }); const [suggests, setSuggests] = useState({ store [], styles [] }); 圓紐名稱映射表 const platformMap = { '抖音' '抖', '小紅書' '紅', '微信' '微', '淘寶' '淘', '閒魚' '閒', '其他' '其' }; const handleInput = (field, val) = { setForm(prev = ({ ...prev, [field] val })); if (val.length 0) { const pool = field === 'storeName' 'storeName' 'styles'; const unique = [...new Set(items.map(i = i[pool]).filter(s = s.toLowerCase().includes(val.toLowerCase()) && s !== val))].slice(0, 5); setSuggests(prev = ({ ...prev, [field === 'storeName' 'store' 'styles'] unique })); } else { setSuggests(prev = ({ ...prev, [field === 'storeName' 'store' 'styles'] [] })); } }; const compressImage = (file) = { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = (e) = { const img = new Image(); img.src = e.target.result; img.onload = () = { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const MAX = 800; let w = img.width, h = img.height; if (w h) { if (w MAX) { h = MAXw; w = MAX; } } else { if (h MAX) { w = MAXh; h = MAX; } } canvas.width = w; canvas.height = h; ctx.drawImage(img, 0, 0, w, h); setForm(prev = ({ ...prev, images [canvas.toDataURL('imagejpeg', 0.6)] })); }; }; }; return ( div className=modal-overlay div className=bg-white p-8 rounded-[3rem] w-full max-w-lg border-4 border-[#EDE7F6] shadow-2xl max-h-[90vh] overflow-y-auto hide-scrollbar relative div className=flex justify-between items-center mb-8 h2 className=text-2xl text-[#7E57C2]{editingItem '編輯寶貝' '新增登記'}h2 button onClick={onClose} className=p-2 bg-gray-50 rounded-full text-gray-400SvgIcon name=x size={18}button div div className=space-y-6 div className=flex flex-col items-center gap-4 div className=w-32 h-32 rounded-3xl bg-[#F3E5F5] border-4 border-dashed border-[#D1C4E9] flex items-center justify-center relative overflow-hidden {form.images.[0] img src={form.images[0]} className=w-full h-full object-cover div className=text-center text-[#9575CD] text-xs📸 上傳照片div} input type=file accept=image onChange={e = e.target.files[0] && compressImage(e.target.files[0])} className=absolute inset-0 opacity-0 cursor-pointer div div { 老闆,平台選擇也改成一個字的圓圓鈕囉 } div className=flex gap-3 overflow-x-auto hide-scrollbar pb-2 {PLATFORMS.map(p = ( button key={p} onClick={() = setForm({...form, platform p})} className={`w-10 h-10 shrink-0 rounded-full text-sm flex items-center justify-center border-2 transition-all ${form.platform === p 'bg-[#9575CD] border-[#9575CD] text-white shadow-md' 'border-purple-100 text-purple-300 bg-white'}`} title={p} {platformMap[p]} button ))} div div className=relative input placeholder=店家名稱 value={form.storeName} onChange={e = handleInput('storeName', e.target.value)} className=w-full text-lg {suggests.store.length 0 && ( div className=suggestion-box {suggests.store.map(s = div key={s} className=suggestion-item onClick={() = { setForm({...form, storeName s}); setSuggests({...suggests, store []}); }}🏢 {s}div)} div )} div div className=relative input placeholder=品名樣式 value={form.styles} onChange={e = handleInput('styles', e.target.value)} className=w-full text-lg {suggests.styles.length 0 && ( div className=suggestion-box {suggests.styles.map(s = div key={s} className=suggestion-item onClick={() = { setForm({...form, styles s}); setSuggests({...suggests, styles []}); }}💎 {s}div)} div )} div div className=grid grid-cols-2 gap-4 div className=flex flex-col gap-1 span className=text-[10px] text-purple-300 ml-2 font-bold下單日期span input type=date value={form.orderDate} onChange={e = setForm({...form, orderDate e.target.value})} className=w-full text-sm div div className=flex flex-col gap-1 span className=text-[10px] text-purple-300 ml-2 font-bold價格 (¥)span input type=number placeholder=¥ 價格 value={form.price} onChange={e = setForm({...form, price e.target.value})} className=w-full text-sm div div div className=grid grid-cols-2 gap-4 select value={form.warehouse} onChange={e = setForm({...form, warehouse e.target.value})} className=text-sm {WAREHOUSES.map(w = option key={w} value={w}{w}option)} select select value={form.status} onChange={e = setForm({...form, status e.target.value})} className=text-sm {Object.values(STATUS).map(s = option key={s} value={s}{s}option)} select div input placeholder=運輸單號 (選填) value={form.trackingNo} onChange={e = setForm({...form, trackingNo e.target.value})} className=w-full text-sm font-mono tracking-wider textarea placeholder=老闆,還有什麼要記下來的嗎? value={form.note} onChange={e = setForm({...form, note e.target.value})} className=w-full text-sm h-24 button onClick={() = onSave(form)} className=w-full py-5 bg-gradient-to-r from-[#9575CD] to-[#D1C4E9] text-white rounded-[1.5rem] text-xl shadow-lg activescale-95 transition-all確認存檔 ✨button div div div ); } function App() { const [items, setItems] = useState([]); const [isLoading, setIsLoading] = useState(true); const [activeTab, setActiveTab] = useState('list'); const [searchQuery, setSearchQuery] = useState(); const [isModalOpen, setIsModalOpen] = useState(false); const [isEZWayOpen, setIsEZWayOpen] = useState(false); const [editingItem, setEditingItem] = useState(null); const [trackingTarget, setTrackingTarget] = useState(null); const shoppingRef = window.db.collection('artifacts').doc(appId).collection('public').doc('data').collection('shopping_items'); useEffect(() = { const init = async () = { await window.auth.signInAnonymously(); shoppingRef.orderBy('createdAt', 'desc').onSnapshot(s = { setItems(s.docs.map(d = ({ id d.id, ...d.data() }))); setIsLoading(false); }); }; init(); }, []); const isHistory = (item) = { if (item.status !== STATUS.RECEIVED) return false; const oneMonthAgo = Date.now() - (30 24 60 60 1000); return (item.updatedAt item.createdAt) oneMonthAgo; }; const filtered = useMemo(() = { const q = searchQuery.toLowerCase().trim(); return items.filter(i = { const matchSearch = !q i.storeName.toLowerCase().includes(q) i.styles.toLowerCase().includes(q) i.trackingNo.toLowerCase().includes(q); if (activeTab === 'history') return matchSearch && isHistory(i); if (activeTab === 'list') return matchSearch && !isHistory(i); if (activeTab === 'gallery') return matchSearch && i.images.[0]; if (activeTab === 'warehouse') return matchSearch && i.status !== STATUS.RECEIVED; return matchSearch; }); }, [items, activeTab, searchQuery]); const groupedByDate = useMemo(() = { return filtered.reduce((acc, i) = { const d = i.orderDate 日期未定; if (!acc[d]) acc[d] = []; acc[d].push(i); return acc; }, {}); }, [filtered]); const handleSave = async (data) = { const { id, ...payload } = data; const update = { ...payload, updatedAt Date.now() }; if (id) await shoppingRef.doc(id).update(update); else await shoppingRef.add({ ...update, createdAt Date.now() }); setIsModalOpen(false); setEditingItem(null); showToast(祕書辦妥了 ✨); }; const updateStatus = async (id, nextStatus) = { if (nextStatus === STATUS.SHIPPED) { setTrackingTarget(id); } else { await shoppingRef.doc(id).update({ status nextStatus, updatedAt Date.now() }); showToast(狀態更新囉 ✨); } }; const confirmShipped = async (no) = { await shoppingRef.doc(trackingTarget).update({ status STATUS.SHIPPED, trackingNo no, updatedAt Date.now() }); setTrackingTarget(null); showToast(發貨成功!已彙整至 EZWay); }; return ( div className=min-h-screen pb-32 header className=sticky top-0 z-[1000] bg-[#FFFDF2]90 backdrop-blur-xl border-b-4 border-[#EDE7F6] p-4 mdp-6 div className=max-w-4xl mx-auto flex flex-col gap-4 div className=flex justify-between items-center div className=flex items-center gap-3 SecretaryIcon h1 className=text-xl mdtext-2xl text-[#7E57C2]購物狂的專屬祕書h1 div div className=bg-white px-4 py-2 rounded-full border-2 border-[#EDE7F6] text-[10px] text-purple-400 {filtered.length} {items.length} 件 div div div className=relative input placeholder=🔍 搜尋店家、品名、單號 (包含歷史)... value={searchQuery} onChange={e = setSearchQuery(e.target.value)} className=w-full pl-12 pr-4 py-3 text-sm shadow-inner SvgIcon name=search className=absolute left-4 top-12 -translate-y-12 text-purple-200 div div className=flex gap-2 overflow-x-auto hide-scrollbar py-1 {['list', 'gallery', 'warehouse', 'history'].map(t = ( button key={t} onClick={() = setActiveTab(t)} className={`px-6 py-2 rounded-full text-xs whitespace-nowrap bg-white border-2 border-[#EDE7F6] text-purple-300 transition-all ${activeTab === t 'active-tab border-transparent' ''}`} {t==='list''全部清單't==='gallery''美照圖庫't==='warehouse''倉庫進度''歷史存摺'} button ))} div div header main className=max-w-4xl mx-auto p-4 mdp-6 {isLoading div className=text-center py-20 opacity-20 animate-pulse正在為老闆準備筆記...div ( activeTab === 'gallery' ( div className=grid grid-cols-2 mdgrid-cols-4 gap-4 {filtered.map(i = ( div key={i.id} onClick={() = {setEditingItem(i); setIsModalOpen(true);}} className=aspect-square y2k-card overflow-hidden img src={i.images[0]} className=w-full h-full object-cover div ))} div ) activeTab === 'warehouse' ( div className=space-y-6 {WAREHOUSES.map(wh = { const whItems = filtered.filter(i = i.warehouse === wh); return whItems.length 0 && ( div key={wh} className=space-y-3 h2 className=text-lg text-purple-300 ml-2 font-bold📍 {wh}h2 div className=grid grid-cols-1 mdgrid-cols-2 gap-4 {whItems.map(i = ( div key={i.id} onClick={() = {setEditingItem(i); setIsModalOpen(true);}} className=y2k-card p-4 flex gap-4 items-center div className=w-16 h-16 rounded-2xl bg-gray-50 shrink-0 overflow-hidden {i.images.[0] && img src={i.images[0]} className=w-full h-full object-cover } div div className=flex-1 min-w-0 div className=flex justify-between items-start p className=text-sm truncate font-bold{i.styles}p span className={`status-tag shrink-0 ${i.status === STATUS.ORDERED 'bg-ordered' i.status === STATUS.SHIPPED 'bg-shipped' 'bg-received'}`}{i.status}span div p className=text-[10px] text-purple-300 font-mono mt-1 font-bold{i.trackingNo '單號待填'}p div div ))} div div ); })} div ) ( div className=space-y-8 {Object.entries(groupedByDate).sort((a,b) = b[0].localeCompare(a[0])).map(([date, list]) = ( div key={date} className=space-y-4 h3 className=text-sm bg-white50 inline-block px-4 py-1 rounded-full text-purple-300 border border-[#EDE7F6] font-bold{date}h3 div className=grid grid-cols-1 mdgrid-cols-2 gap-5 {list.map(item = ( div key={item.id} onClick={() = {setEditingItem(item); setIsModalOpen(true);}} className=y2k-card p-5 relative group div className=flex gap-4 div className=w-20 h-20 rounded-[1.5rem] bg-[#F3E5F5] border-2 border-purple-50 shrink-0 overflow-hidden {item.images.[0] && img src={item.images[0]} className=w-full h-full object-cover } div div className=flex-1 min-w-0 div className=flex justify-between items-start mb-1 span className=text-[10px] opacity-40 uppercase tracking-tighter font-bold{item.platform}span div className={`status-tag ${item.status === STATUS.ORDERED 'bg-ordered' item.status === STATUS.SHIPPED 'bg-shipped' 'bg-received'}`}{item.status}div div h4 className=text-lg leading-tight mb-1 truncate font-bold{item.styles}h4 p className=text-xs text-purple-300 font-bold{item.storeName}p div div div className=mt-4 pt-4 border-t border-[#EDE7F6] flex justify-between items-center span className=text-[11px] font-mono text-purple-200 tracking-wider font-bold{item.trackingNo 陸運單號未填}span div className=flex gap-3 {item.status === STATUS.ORDERED && ( button onClick={(e) = {e.stopPropagation(); updateStatus(item.id, STATUS.SHIPPED);}} className=w-8 h-8 rounded-full bg-purple-50 text-[#7E57C2] flex items-center justify-center text-sm activescale-90 transition-all font-bold border border-purple-100 shadow-sm title=發貨 發 button )} {item.status === STATUS.SHIPPED && ( button onClick={(e) = {e.stopPropagation(); updateStatus(item.id, STATUS.RECEIVED);}} className=w-8 h-8 rounded-full bg-mint20 text-[#00897B] flex items-center justify-center text-sm activescale-90 transition-all font-bold border border-[#B2DFDB] shadow-sm title=簽收 收 button )} button onClick={(e) = {e.stopPropagation(); if(confirm('要丟掉這件寶貝嗎?')) shoppingRef.doc(item.id).delete();}} className=p-1.5 text-gray-200 hovertext-red-400 transition-colorsSvgIcon name=trash size={14}button div div div ))} div div ))} div ) )} main div className=fixed bottom-8 right-8 flex flex-col gap-4 items-end z-[5000] button onClick={() = setIsEZWayOpen(true)} className=strawberry-float w-14 h-14 bg-white rounded-full shadow-xl flex items-center justify-center border-4 border-[#FFD1DC] activescale-90 transition-all SecretaryIcon className=w-8 h-8 button button onClick={() = {setEditingItem(null); setIsModalOpen(true);}} className=w-16 h-16 bg-[#D1C4E9] rounded-full shadow-2xl text-[#4527A0] text-4xl flex items-center justify-center border-4 border-white activescale-90 transition-all font-bold + button div {isModalOpen && AddEditModal onClose={() = setIsModalOpen(false)} onSave={handleSave} editingItem={editingItem} items={items} } {trackingTarget && TrackingModal onConfirm={confirmShipped} onClose={() = setTrackingTarget(null)} } {isEZWayOpen && EZWayModal items={items} onClose={() = setIsEZWayOpen(false)} } div ); } const root = ReactDOM.createRoot(document.getElementById('root')); root.render(App ); script body html