Repository navigation
Expand file tree
/
Copy pathmain.html
More file actions
49 lines (49 loc) · 18.7 KB
/
Copy pathmain.html
File metadata and controls
49 lines (49 loc) · 18.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!DOCTYPE html>
<html lang='zh-Hant'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1, viewport-fit=cover'>
<title>剩食媒合 Demo</title>
<meta name='theme-color' content='#01696f'>
<script src='https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js'></script>
<style>
:root{--bg:#f7f6f2;--surface:#fff;--surface2:#faf9f6;--text:#28251d;--muted:#6d6a63;--line:#e7e2d9;--primary:#01696f;--primarySoft:#dceceb;--ok:#437a22;--okSoft:#e5efdd;--warn:#da7101;--warnSoft:#f6e6d8;--blue:#0a6173;--blueSoft:#e4f1f4;--shadow:0 10px 30px rgba(40,37,29,.08);--r:24px;--r2:18px}
*{box-sizing:border-box;margin:0;padding:0}html{-webkit-text-size-adjust:none;text-size-adjust:none}body{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Noto Sans TC',sans-serif;background:linear-gradient(180deg,#ece8e0,#f7f6f2);color:var(--text)}button,input,textarea{font:inherit}button{cursor:pointer}
.app{max-width:430px;margin:0 auto;min-height:100vh;background:var(--bg);position:relative;padding-bottom:96px;box-shadow:0 24px 60px rgba(40,37,29,.12)}
.screen{display:none;min-height:100vh}.screen.active{display:block}.top{position:sticky;top:0;background:rgba(247,246,242,.94);backdrop-filter:blur(12px);padding:16px;border-bottom:1px solid rgba(0,0,0,.06);z-index:10}.row{display:flex;align-items:center;justify-content:space-between;gap:10px}.title{font-size:1.12rem;font-weight:800}.sub{font-size:.84rem;color:var(--muted);margin-top:4px}.content{padding:16px;display:grid;gap:16px}.card{background:var(--surface);border:1px solid rgba(0,0,0,.06);border-radius:var(--r);box-shadow:var(--shadow);padding:16px}.hero{background:linear-gradient(155deg,#f4f1ea,#fff 45%,#dceceb 100%)}.hero h1{font-size:1.74rem;line-height:1.08;margin-bottom:10px}.hero p{color:var(--muted);margin-bottom:14px}.btns{display:flex;gap:10px;flex-wrap:wrap}.btn{height:46px;border:none;border-radius:999px;padding:0 16px;font-weight:800}.btn.primary{background:var(--primary);color:#fff}.btn.secondary{background:#fff;border:1px solid rgba(0,0,0,.1)}
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.stat{background:var(--surface);border:1px solid rgba(0,0,0,.06);border-radius:18px;padding:12px}.stat span{display:block;color:var(--muted);font-size:.74rem;margin-bottom:4px}.stat strong{font-size:.94rem}
.search{display:flex;align-items:center;gap:10px;border:1px solid rgba(0,0,0,.08);background:var(--surface);border-radius:999px;padding:0 14px;min-height:48px}.search input{border:none;outline:none;background:transparent;width:100%}
.tabs,.chips,.segments{display:flex;gap:8px;overflow:auto;padding-bottom:2px}.tab,.chip,.segment{white-space:nowrap;border:none;background:var(--surface);border:1px solid rgba(0,0,0,.08);border-radius:999px;padding:9px 14px;color:var(--muted);font-weight:700}.tab.active,.segment.active{background:var(--primary);color:#fff;border-color:var(--primary)}.chip.active{background:var(--primarySoft);color:var(--primary);border-color:transparent}
.feed{display:grid;gap:16px}.feedCard{padding:0;overflow:hidden}.cover{height:170px;background:linear-gradient(135deg,#dceceb,#f5ede2 55%,#efe3d0);position:relative}.badges{position:absolute;top:12px;left:12px;right:12px;display:flex;gap:8px;flex-wrap:wrap}.badge{background:rgba(255,255,255,.92);border-radius:999px;padding:6px 10px;font-size:.75rem;font-weight:800}.badge.ok{background:var(--okSoft);color:var(--ok)}.badge.warn{background:var(--warnSoft);color:#8a4a09}.float{position:absolute;background:#fff;border-radius:999px;padding:8px 10px;font-size:.76rem;font-weight:800;box-shadow:0 6px 18px rgba(0,0,0,.08)}.f1{left:16px;top:62px}.f2{right:16px;top:80px}.f3{left:28px;bottom:18px}.body{padding:16px;display:grid;gap:12px}.muted{color:var(--muted);font-size:.88rem}.safe{display:flex;justify-content:space-between;gap:10px;background:var(--primarySoft);color:var(--primary);border-radius:16px;padding:16px}.safe span{display:block;font-size:.95rem;font-weight:700}.safe strong{font-size:1.2rem}.meta{display:flex;gap:8px;flex-wrap:wrap}.pill{background:var(--surface2);color:var(--muted);border-radius:999px;padding:6px 10px;font-size:.75rem}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:10px}.mini{background:var(--surface2);border-radius:16px;padding:12px}.mini span{display:block;font-size:.75rem;color:var(--muted);margin-bottom:4px}.mini strong{font-size:.95rem}.warnBox,.infoBox,.okBox{border-radius:16px;padding:12px;font-size:.92rem}.warnBox{background:var(--warnSoft);color:#8a4a09}.infoBox{background:var(--blueSoft);color:var(--blue)}.okBox{background:var(--okSoft);color:var(--ok)}
#reader{width:100%;min-height:280px;background:#111;border-radius:24px;overflow:hidden}.hint{font-size:.82rem;color:var(--muted)}
.input{display:flex;align-items:center;min-height:48px;border:1px solid rgba(0,0,0,.08);background:#fff;border-radius:999px;padding:0 14px}.input input{width:100%;border:none;outline:none;background:transparent}.textarea{border:1px solid rgba(0,0,0,.08);background:#fff;border-radius:20px;padding:12px}.textarea textarea{width:100%;min-height:100px;border:none;outline:none;resize:none;background:transparent}.sectionTitle{font-size:1rem;font-weight:800}.avatar{width:56px;height:56px;border-radius:18px;background:linear-gradient(145deg,var(--primary),#2d8f95);display:grid;place-items:center;color:#fff;font-size:1.1rem;font-weight:900}.nav{position:fixed;left:50%;transform:translateX(-50%);bottom:0;max-width:430px;width:100%;display:flex;justify-content:space-around;background:rgba(249,248,245,.97);backdrop-filter:blur(12px);padding:10px 12px 18px;border-top:1px solid rgba(0,0,0,.08)}.nav button{border:none;background:none;color:#807d76;font-size:.72rem;display:grid;place-items:center;gap:4px;font-weight:700}.nav button.active{color:var(--primary)}
@media (max-width:520px){.app{max-width:none;width:100%;box-shadow:none}.nav{max-width:none}}
</style>
</head>
<body>
<main class='app'>
<section class='screen active' id='home'>
<header class='top'><div class='row'><div><div class='title'>附近剩食</div></div><button class='btn secondary' data-go='records'>通知</button></div></header>
<div class='content'>
<article class='card hero'><h1>掃描實際 QR code 後,直接跳出剩餘時間。</h1><p> </p><div class='btns'><button class='btn primary' data-go='scan'>開始掃描</button><button class='btn secondary' data-go='detail'>查看結果頁</button></div></article>
<div class='stats'><article class='stat'><span>附近貼文</span><strong>12 則</strong></article><article class='stat'><span>可領取</span><strong>43 份</strong></article><article class='stat'><span>已備 QR</span><strong>2 張</strong></article></div>
<div class='search'><span>🔎</span><input placeholder='搜尋地點、活動名稱、主辦單位'></div>
<div class='tabs'><button class='tab active'>最近優先</button><button class='tab'>最新發布</button><button class='tab'>剩餘份數多</button></div>
<div class='chips'><button class='chip'>100m</button><button class='chip active'>300m</button><button class='chip'>500m</button><button class='chip'>1km</button></div>
<div class='feed'>
<article class='card feedCard'><div class='cover'><div class='badges'><span class='badge'>距離 120m</span><span class='badge'>剩 8 份</span><span class='badge ok'>可領取</span></div><div class='float f1'>領取點 A</div><div class='float f2'>3 分鐘</div><div class='float f3'>雞腿便當</div></div><div class='body'><div><div class='sectionTitle'>講座活動剩餘餐盒</div><div class='muted'>活動會場 A 入口取餐桌</div></div><div class='meta'><span class='pill'>5 分鐘前發布</span><span class='pill'>17:30 前領取</span><span class='pill'>雞腿 / 素食便當</span></div><div class='btns'><button class='btn primary quick-claim' data-food='talk-box'>我要領取</button></div></div></article>
<article class='card feedCard'><div class='cover'><div class='badges'><span class='badge'>距離 240m</span><span class='badge'>剩 6 份</span><span class='badge warn'>冷藏品</span></div><div class='float f1'>領取點 B</div><div class='float f2'>5 分鐘</div><div class='float f3'>沙拉餐盒</div></div><div class='body'><div><div class='sectionTitle'>活動說明會餐盒釋出</div><div class='muted'>中央活動區服務台旁</div></div><div class='btns'><button class='btn primary quick-claim' data-food='salad-box'>我要領取</button></div></div></article>
</div>
</div></section>
<section class='screen' id='detail'><header class='top'><div class='title'>餐盒詳細資訊</div><div class='sub'>掃描成功後這頁會自動更新</div></header><div class='content'><article class='card'><div class='row'><strong id='itemName' style='font-size:1.2rem'>雞腿便當</strong><span class='badge ok' id='statusText'>建議可領取</span></div><div class='sub' id='sourceText' style='margin-top:6px'>來源:合作供餐單位</div><div style='height:14px'></div><div class='grid2'><div class='mini'><span>QR 編號</span><strong id='codeText'>BX-10428</strong></div><div class='mini'><span>保存方式</span><strong id='storageText'>常溫暫存</strong></div><div class='mini'><span>出餐時間</span><strong id='servedText'>16:18</strong></div><div class='mini'><span>掃碼時間</span><strong id='scannedText'>--:--</strong></div><div class='mini'><span>風險等級</span><strong id='riskText'>中低風險</strong></div><div class='mini'><span>領取截止</span><strong id='pickupText'>17:30</strong></div></div><div style='height:12px'></div><div class='safe'><div><span>已出餐經過</span><strong id='elapsedText'>42 分鐘</strong></div><div><span>建議剩餘可食時間</span><strong id='remainingText'>約 35 分鐘</strong></div></div><div style='height:12px'></div><div class='okBox' id='scanMessage'>尚未掃描,請到掃碼頁掃描貼在餐盒上的實際 QR code。</div><div style='height:12px'></div><div class='warnBox' id='warningText'>含蛋與肉類主菜,若已領取請盡快食用。</div></article></div></section>
<section class='screen' id='scan'><header class='top'><div class='title'>掃描實際 QR code</div><div class='sub'>允許相機後即可直接掃描貼在餐盒上的 QR</div></header><div class='content'><article class='card'><div id='reader'></div><div style='height:12px'></div><div class='hint'>Demo 模式:只要偵測到任何 QR code,就直接回傳一組示範餐盒資料與剩餘食用時間。</div><div style='height:12px'></div><div class='btns'><button class='btn secondary' id='startBtn'>啟動相機</button><button class='btn secondary' id='stopBtn'>停止掃描</button><button class='btn primary' id='showResultBtn'>查看最近結果</button></div></article></div></section>
<section class='screen' id='records'><header class='top'><div class='title'>紀錄與通知</div><div class='sub'>追蹤掃碼與領取結果</div></header><div class='content'><div class='segments'><button class='segment active'>掃碼紀錄</button><button class='segment'>我的貼文</button><button class='segment'>領取紀錄</button></div><article class='card'><div class='sectionTitle'>最近掃描結果</div><div class='sub' id='recentSummary' style='margin-top:8px'>尚未掃描任何 QR code。</div></article><article class='card'><div class='sectionTitle'>通知</div><div class='sub' style='margin-top:8px'>掃碼功能已啟用,可於 Demo 時直接掃描實體 QR code。</div></article></div></section>
<section class='screen' id='profile'><header class='top'><div class='title'>我的</div><div class='sub'>角色與偏好設定</div></header><div class='content'><article class='card'><div class='row' style='justify-content:flex-start;gap:12px'><div class='avatar'>L</div><div><div class='sectionTitle'>使用者 Demo</div><div class='sub'>角色:待領取者</div></div></div><div style='height:14px'></div><div class='grid2'><div class='mini'><span>本月領取</span><strong>7 次</strong></div><div class='mini'><span>本月發文</span><strong>2 則</strong></div><div class='mini'><span>常用範圍</span><strong>300m</strong></div><div class='mini'><span>預設排序</span><strong>最近優先</strong></div></div></article><article class='card'><div class='sectionTitle'>⚖️ 弱勢族群登記</div><div class='sub' style='margin-top:8px;margin-bottom:12px'>登記後會在剩食分配時獲得優先權重(後台默默計算,不會公開標示)。</div><div style='margin-top:12px'><div class='sub'>弱勢類別</div><select id='vulnerableSelect' style='width:100%;height:46px;border:1px solid rgba(0,0,0,.08);border-radius:999px;padding:0 14px;background:#fff;font:inherit;margin-top:6px'><option value=''>請選擇</option><option value='low-income'>低所得家庭</option><option value='student'>在學學生</option><option value='elderly'>獨居長者</option><option value='disabled'>身心障礙者</option><option value='refugee'>新住民家庭</option></select></div><div style='margin-top:12px'><button class='btn primary' id='registerVulnerableBtn' style='width:100%;height:48px'>登記為弱勢族群</button></div><div class='okBox' id='vulnerableStatus' style='margin-top:12px;text-align:center;display:none'>已登記 ✓</div></article></div></section>
<nav class='nav'><button class='active' data-go='home'>首頁</button><button data-go='scan'>掃碼</button><button data-go='records'>紀錄</button><button data-go='profile'>我的</button></nav>
</main>
<script>
const screens=document.querySelectorAll('.screen');const navButtons=document.querySelectorAll('.nav button');const gotoButtons=document.querySelectorAll('[data-go]');let scanner=null,scannerRunning=false,lastScan=null;const presetFoods={'talk-box':{id:'POST-10018',item:'講座活動剩餘餐盒',servedAt:new Date(Date.now()-42*60000).toISOString(),storage:'room',pickupBy:'17:30',risk:'中低風險',source:'活動會場 A 入口取餐桌',warning:'含蛋與肉類主菜,若已領取請盡快食用。'},'salad-box':{id:'POST-20811',item:'沙拉餐盒',servedAt:new Date(Date.now()-18*60000).toISOString(),storage:'cold',pickupBy:'17:20',risk:'中高風險',source:'中央活動區服務台旁',warning:'含生菜與冷食,若離開冷藏環境請盡速食用。'}};function go(id){screens.forEach(s=>s.classList.toggle('active',s.id===id));navButtons.forEach(b=>b.classList.toggle('active',b.dataset.go===id));if(id==='scan'){startScanner()}else{stopScanner()}window.scrollTo({top:0,behavior:'smooth'})}gotoButtons.forEach(btn=>btn.addEventListener('click',()=>{const t=btn.dataset.go;if(t)go(t)}));function minsBetween(iso){return Math.max(0,Math.floor((Date.now()-new Date(iso).getTime())/60000))}function compute(p){let e=minsBetween(p.servedAt),base=120;if(p.storage==='cold')base=90;if(/沙拉|冷食|乳|海鮮/.test(p.item))base-=20;let r=Math.max(0,base-e),s='建議可領取';if(r<=20)s='建議儘速食用';if(r<=5)s='不建議領取';return{elapsed:e,remaining:r,status:s}}function render(p){const c=compute(p),n=new Date();itemName.textContent=p.item;statusText.textContent=c.status;sourceText.textContent='來源:'+p.source;codeText.textContent=p.id;storageText.textContent=p.storage==='cold'?'冷藏保存':'常溫暫存';servedText.textContent=new Date(p.servedAt).toLocaleTimeString('zh-TW',{hour:'2-digit',minute:'2-digit',hour12:false});scannedText.textContent=n.toLocaleTimeString('zh-TW',{hour:'2-digit',minute:'2-digit',hour12:false});riskText.textContent=p.risk||'中低風險';pickupText.textContent=p.pickupBy||'--:--';elapsedText.textContent=c.elapsed+' 分鐘';remainingText.textContent='約 '+c.remaining+' 分鐘';scanMessage.textContent='掃描成功:'+p.item+',目前建議剩餘可食時間約 '+c.remaining+' 分鐘。';warningText.textContent=p.warning||'請盡快食用。';recentSummary.textContent=p.item+'('+p.id+')最近一次掃描結果:剩餘約 '+c.remaining+' 分鐘。';lastScan=p}function demoPayloadFromAnyQR(t){const foods=[{item:'雞腿便當',storage:'room',risk:'中低風險',source:'合作供餐單位',warning:'含蛋與肉類主菜,若已領取請盡快食用。'},{item:'素食便當',storage:'room',risk:'低風險',source:'合作供餐單位',warning:'建議於短時間內食用完畢。'},{item:'沙拉餐盒',storage:'cold',risk:'中高風險',source:'冷藏輕食供應單位',warning:'含生菜與冷食,若離開冷藏環境請盡速食用。'},{item:'三明治餐盒',storage:'cold',risk:'中風險',source:'冷藏輕食供應單位',warning:'含冷藏配料,請避免久放。'}];const seed=(t||'demo').split('').reduce((a,c)=>a+c.charCodeAt(0),0),food=foods[seed%foods.length],now=Date.now(),elapsed=15+(seed%35),servedAt=new Date(now-elapsed*60000).toISOString(),pickupBy=new Date(now+(20+(seed%30))*60000).toLocaleTimeString('zh-TW',{hour:'2-digit',minute:'2-digit',hour12:false});return{id:'DEMO-'+String(seed).padStart(5,'0').slice(-5),item:food.item,servedAt,storage:food.storage,pickupBy,risk:food.risk,source:food.source,warning:food.warning}}async function onScanSuccess(decodedText){render(demoPayloadFromAnyQR(decodedText));go('detail')}async function startScanner(){if(scannerRunning)return;try{if(!scanner)scanner=new Html5Qrcode('reader');await scanner.start({facingMode:'environment'},{fps:10,qrbox:{width:220,height:220}},onScanSuccess,()=>{});scannerRunning=true}catch(err){reader.innerHTML='<div class="warnBox">無法開啟相機。請確認使用 HTTPS、已允許相機權限。</div>'}}async function stopScanner(){if(scanner&&scannerRunning){try{await scanner.stop();await scanner.clear()}catch(e){}scannerRunning=false;reader.innerHTML=''}}document.querySelectorAll('.quick-detail').forEach(btn=>btn.addEventListener('click',()=>{const p=presetFoods[btn.dataset.food];if(p){render(p);go('detail')}}));document.querySelectorAll('.quick-claim').forEach(btn=>btn.addEventListener('click',()=>{const p=presetFoods[btn.dataset.food];if(p){render(p);scanMessage.textContent='你已選擇領取:'+p.item+',請於 '+p.pickupBy+' 前到指定地點領取。';alert('已登記!你已選擇領取:'+p.item+',請於 '+p.pickupBy+' 前到指定地點領取。');go('detail')}}));document.getElementById('foodImage').addEventListener('change',e=>{const file=e.target.files[0];if(file){alert('圖片已上傳:'+file.name)}});document.getElementById('registerVulnerableBtn').addEventListener('click',()=>{const select=document.getElementById('vulnerableSelect');if(!select.value){alert('請選擇弱勢類別');return}document.getElementById('vulnerableStatus').style.display='block';document.getElementById('vulnerableStatus').textContent='已登記為 '+select.options[select.selectedIndex].text+',獲得優先權重 ✓';select.disabled=true;document.getElementById('registerVulnerableBtn').disabled=true;document.getElementById('registerVulnerableBtn').textContent='已登記'});startBtn.addEventListener('click',startScanner);stopBtn.addEventListener('click',stopScanner);showResultBtn.addEventListener('click',()=>{if(lastScan)go('detail');else alert('目前還沒有掃描結果。')});
</script>
</body>
</html>