「透過通路合作,讓防災物資能夠更輕易的購入,提升防災物資準備率。」
在專案的前半段,我們已經討論過前端設計、後端API、資料庫模型與AI推薦邏輯。
下一個現實問題是:
這些物資,使用者要去哪裡買?
若是本專案最後僅以「一份靜態防災物資建議清單」作收,則稍嫌可惜。
為使用者建立一段更容易採購的路徑,方能夠如 「韌性生活指南」 Day1所說: 「日常有備,災來無懼」 。
這也是,今天要探討的一個關鍵環節: 設計一個可行的電商合作策略 。
以策略層面來說,我把電商合作分為三個層次:
1.短期(Demo/原型製作階段)
在技術設計層面,我提出了第一個落地方案:
好處:技術門檻低、能快速上線/體驗、維持版面的整潔、專注在資訊傳達的正確性。
限制:需要人工維護連結,無法自動更新商品庫存或價格。
target="_blank"
+ rel="noopener noreferrer"
),並建議加上 UTM追蹤參數 。貼到一個
.html
檔即可跑;把LINKS
裡的網址換成你實際整理的頁面或站內搜尋結果。
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>韌性生活指南|物資卡片示例</title>
<style>
body { font-family: system-ui, "Noto Sans TC", sans-serif; background:#f7f7f8; padding:24px; color:#111;}
.card { background:#fff; border-radius:16px; padding:20px; max-width:820px; margin:auto; box-shadow:0 6px 24px rgba(0,0,0,.06);}
.row { display:flex; gap:16px; align-items:flex-start; flex-wrap:wrap;}
.thumb { width:96px; height:96px; border-radius:12px; background:#eef2f7 url('') center/60% no-repeat; flex:0 0 96px; }
h2 { margin:0 0 4px; font-size:22px;}
.meta { color:#666; font-size:14px; margin:4px 0;}
.desc { margin:12px 0 20px; color:#333; line-height:1.6;}
.divider { border-top:1px solid #eee; margin:16px 0;}
.shops { display:flex; gap:12px; flex-wrap:wrap;}
.shop-btn { display:flex; align-items:center; justify-content:center; gap:8px;
padding:12px 16px; border:1px solid #e5e7eb; border-radius:999px; background:#fff;
cursor:pointer; transition:.2s; min-width:160px; }
.shop-btn:hover { transform: translateY(-1px); box-shadow:0 6px 16px rgba(0,0,0,.06);}
.shop-logo { height:20px; }
/* Modal */
.modal-backdrop{ position:fixed; inset:0; background:rgba(17,24,39,.55); display:none; align-items:center; justify-content:center; z-index:40;}
.modal{ width:min(520px, 92vw); background:#fff; border-radius:16px; padding:20px 20px 16px; box-shadow:0 20px 60px rgba(0,0,0,.2);}
.modal h3{ margin:0 0 8px; font-size:18px;}
.modal ul{ margin:8px 0 16px 18px; color:#444; }
.modal .danger{ color:#b91c1c; font-weight:700;}
.actions{ display:flex; gap:10px; justify-content:flex-end; }
.btn{ padding:10px 14px; border-radius:10px; border:1px solid #e5e7eb; cursor:pointer; background:#fff;}
.btn.primary{ background:#111827; color:#fff; border-color:#111827;}
</style>
</head>
<body>
<div class="card" id="item-card">
<div class="row">
<div class="thumb" style="background-image:url('https://img.icons8.com/ios-filled/100/water.png'); background-size:54%"></div>
<div style="flex:1">
<div class="meta">分類:食品</div>
<h2>用水</h2>
<div class="meta"><strong>所需數量:</strong><span id="need-text">17 公升</span></div>
<p class="desc">一個人維持生命所需的水量會因年齡和體重而不同,但建議每人每天的飲用水量為 3 公升。</p>
</div>
</div>
<div class="divider"></div>
<div>
<div class="meta" style="margin-bottom:8px;">線上購買</div>
<div class="shops" id="shop-buttons"></div>
</div>
</div>
<!-- 免責彈窗 -->
<div class="modal-backdrop" id="modal">
<div class="modal" role="dialog" aria-modal="true">
<h3>筆記</h3>
<ul>
<li><span class="danger">從現在起,該網站不再由本平台管理或經營。</span></li>
<li>本平台對於於第三方網站購買之產品所造成的任何損害或問題不承擔任何責任。</li>
<li>請小心分辨第三方網站與其產品資訊。</li>
</ul>
<div class="actions">
<button class="btn" id="btn-cancel">取消</button>
<button class="btn primary" id="btn-continue">同意並進入下一頁</button>
</div>
</div>
</div>
<script>
// 1) 物資選品設定(無 API 版)
// 你可以為每個物資定義不同平台的目標連結(精選商品或站內搜尋)
const LINKS = {
water: {
momo: {
label: "momo",
// 例:精選商品或搜尋結果(建議加上 UTM 以追蹤來源)
url: "https://www.momoshop.com.tw/search/searchShop.jsp?keyword=%E7%93%B6%E8%A3%9D%E6%B0%B4&utm_source=rensheng&utm_medium=referral&utm_campaign=water",
logo: "https://seeklogo.com/images/M/momo-logo-6F1F5D7C2B-seeklogo.com.png"
},
pchome: {
label: "PChome 24h",
url: "https://24h.pchome.com.tw/store/DSAJ2L?utm_source=rensheng&utm_medium=referral&utm_campaign=water",
logo: "https://upload.wikimedia.org/wikipedia/commons/5/5f/PChome24h.png"
},
eslite: {
label: "誠品線上",
// 若誠品未有對應商品,可改成站內搜尋或你們談好的專區
url: "https://www.eslite.com/Search?keyword=%E6%B0%B4%20%E6%8A%97%E7%81%BD&utm_source=rensheng&utm_medium=referral&utm_campaign=water",
logo: "https://upload.wikimedia.org/wikipedia/commons/3/35/Eslite_logo.svg"
}
}
};
// 2) 建立按鈕(用 water 作為示範)
const shopsEl = document.getElementById('shop-buttons');
const platforms = LINKS.water;
let pendingUrl = null;
Object.keys(platforms).forEach(key => {
const { label, url, logo } = platforms[key];
const btn = document.createElement('button');
btn.className = 'shop-btn';
btn.innerHTML = `<img class="shop-logo" src="${logo}" alt="${label}" /> <span>${label}</span>`;
btn.addEventListener('click', () => openDisclaimer(url));
shopsEl.appendChild(btn);
});
// 3) 免責流程:先顯示彈窗,按「同意」才外連
const modal = document.getElementById('modal');
const btnCancel = document.getElementById('btn-cancel');
const btnContinue = document.getElementById('btn-continue');
function openDisclaimer(url) {
pendingUrl = url;
modal.style.display = 'flex';
}
btnCancel.onclick = () => { modal.style.display = 'none'; pendingUrl = null; };
btnContinue.onclick = () => {
if (pendingUrl) {
// 新分頁開啟 + 安全屬性
window.open(pendingUrl, '_blank', 'noopener,noreferrer');
}
modal.style.display = 'none';
pendingUrl = null;
};
// 4) 依你的人數/天數動態換算所需量(此處僅示例)
// 例如:每人每天 3 公升、備 5 天;顯示在 #need-text
const people = 1, days = 6, perDayLiters = 3;
document.getElementById('need-text').textContent = `${people * days * perDayLiters} 公升`;
</script>
</body>
</html>
1.LINKS
物件:把各平台的連結換成我整理好的「商品頁」或「站內搜尋」網址(記得加 UTM)。
2.logo
圖檔:可換成我本地或CDN的品牌ICON。
3.「所需數量」換算:把示範公式替換成我實際的邏輯。
礦泉水 600ml 整箱
、保久水
、淨水濾芯
。LINKS
改成該專區 URL,一次呈現所有物資。<resilience-item>
來承載「物資卡片」更便於管理導購連結,降低因為人工維護過於繁瑣而卡關。
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8"/>
<title>韌性生活指南|Web Component Demo</title>
<script>
class ResilienceItem extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
}
connectedCallback() {
const data = JSON.parse(this.getAttribute("data"));
this.render(data);
}
render(data) {
const style = `
<style>
.card { background:#fff; border-radius:16px; padding:20px; box-shadow:0 6px 24px rgba(0,0,0,.06); max-width:780px; }
h2 { margin:0 0 4px; font-size:20px;}
.meta{color:#666; font-size:14px;}
.desc{margin:10px 0 16px; line-height:1.6;}
.shops{display:flex; gap:12px; flex-wrap:wrap;}
.shop-btn{border:1px solid #e5e7eb; border-radius:999px; padding:10px 14px; background:#fff; cursor:pointer; transition:.2s; display:flex; align-items:center; gap:6px;}
.shop-btn:hover{box-shadow:0 4px 12px rgba(0,0,0,.1);}
.modal-backdrop{position:fixed; inset:0; background:rgba(0,0,0,.55); display:none; align-items:center; justify-content:center; z-index:999;}
.modal{background:#fff; padding:20px; border-radius:12px; max-width:520px;}
.actions{display:flex; gap:10px; justify-content:flex-end; margin-top:12px;}
.btn{padding:8px 14px; border-radius:8px; border:1px solid #ddd; cursor:pointer;}
.btn.primary{background:#111; color:#fff;}
</style>`;
const shopsHTML = data.shops.map(s => `
<button class="shop-btn" data-url="${s.url}">
<img src="${s.logo}" alt="${s.name}" height="18"/> ${s.name}
</button>`).join("");
const template = `
<div class="card">
<div class="meta">分類:${data.category}</div>
<h2>${data.name}</h2>
<div class="meta"><strong>所需數量:</strong>${data.need}</div>
<p class="desc">${data.desc}</p>
<div class="shops">${shopsHTML}</div>
</div>
<div class="modal-backdrop" id="modal">
<div class="modal">
<h3>筆記</h3>
<ul>
<li><span style="color:#b91c1c;font-weight:700">從現在起,該網站不再由本平台管理或經營。</span></li>
<li>本平台對於於第三方網站購買之產品所造成的任何損害或問題不承擔任何責任。</li>
<li>請小心分辨第三方網站與其產品資訊。</li>
</ul>
<div class="actions">
<button class="btn" id="btn-cancel">取消</button>
<button class="btn primary" id="btn-continue">同意並進入下一頁</button>
</div>
</div>
</div>
`;
this.shadowRoot.innerHTML = style + template;
// modal 邏輯
const modal = this.shadowRoot.querySelector("#modal");
const btnCancel = this.shadowRoot.querySelector("#btn-cancel");
const btnContinue = this.shadowRoot.querySelector("#btn-continue");
let pendingUrl = null;
this.shadowRoot.querySelectorAll(".shop-btn").forEach(btn => {
btn.addEventListener("click", () => {
pendingUrl = btn.getAttribute("data-url");
modal.style.display = "flex";
});
});
btnCancel.onclick = () => { modal.style.display = "none"; pendingUrl = null; };
btnContinue.onclick = () => {
if (pendingUrl) window.open(pendingUrl, "_blank", "noopener,noreferrer");
modal.style.display = "none"; pendingUrl = null;
};
}
}
customElements.define("resilience-item", ResilienceItem);
</script>
</head>
<body style="font-family:sans-serif;background:#f7f7f8;padding:20px">
<resilience-item data='{
"category":"食品",
"name":"用水",
"need":"17 公升",
"desc":"建議每人每天的飲用水量為 3 公升。",
"shops":[
{"name":"momo","url":"https://www.momoshop.com.tw/search/searchShop.jsp?keyword=%E7%93%B6%E8%A3%9D%E6%B0%B4","logo":"https://seeklogo.com/images/M/momo-logo-6F1F5D7C2B-seeklogo.com.png"},
{"name":"PChome","url":"https://24h.pchome.com.tw/store/DSAJ2L","logo":"https://upload.wikimedia.org/wikipedia/commons/5/5f/PChome24h.png"},
{"name":"誠品線上","url":"https://www.eslite.com/Search?keyword=%E6%B0%B4","logo":"https://upload.wikimedia.org/wikipedia/commons/3/35/Eslite_logo.svg"}
]
}'></resilience-item>
</body>
</html>
階段性目標:先證明現在的「可行性」,爭取未來的「可擴充性」。
電商合作不是附加功能,更是讓「韌性生活指南」真正走進日常生活的關鍵。
它讓平台不僅能給予提醒,更能化為具體行動, 縮短從「意識」到「實踐」的距離 。
因為準備防災物資,從來不只是一個清單的問題,而是一連串「行動」的組成。
這是我在構思「Day 16|導購方式:如何簡化防災物資採購門檻?」時,回想起發想此專案的初衷,嘗試建立的導購策略和通路合作發展方向:如何讓「韌性生活指南」透過電商合作,走得更穩、更遠。