Skip to content

Instantly share code, notes, and snippets.

@joannewang0807-prog
Created January 31, 2026 05:53
Show Gist options
  • Select an option

  • Save joannewang0807-prog/198f793724793a26320e9bd03cd05565 to your computer and use it in GitHub Desktop.

Select an option

Save joannewang0807-prog/198f793724793a26320e9bd03cd05565 to your computer and use it in GitHub Desktop.
2026
<!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>家族旅遊同步 2026</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root { --lake-green: #78a5a3; --soft-bg: #f5f7f6; }
body { background-color: var(--soft-bg); color: #4a4a4a; font-family: "Microsoft JhengHei", sans-serif; }
.hide-scrollbar::-webkit-scrollbar { display: none; }
.active-day { background-color: #78a5a3 !important; color: white !important; transform: scale(1.05); }
.sync-btn-active { animation: spin 1s linear infinite; }
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
/* 橘紅奔馬圖示 */
.horse-icon {
width: 48px;
height: auto;
margin-bottom: 2px;
}
</style>
</head>
<body class="max-w-md mx-auto min-h-screen relative flex flex-col shadow-inner">
<div id="app" class="flex-grow pb-28">
<div class="bg-teal-700 text-white text-[11px] py-2 px-4 flex justify-between items-center shadow-md">
<span><i class="fas fa-users mr-1"></i> 王敏苓家族同步中</span>
<button @click="syncCloud" class="flex items-center bg-teal-600 px-3 py-1 rounded-full text-[10px]">
<i class="fas fa-sync-alt mr-1" :class="{'sync-btn-active': syncing}"></i>
{{ syncing ? '同步中...' : '發布至雲端' }}
</button>
</div>
<header class="p-6 pt-10 flex justify-between items-end">
<div>
<h1 class="text-3xl font-bold tracking-widest text-gray-700">{{ currentCity }}</h1>
<p class="text-[11px] text-gray-400 mt-1 uppercase">2026 / {{ currentRange }}</p>
</div>
<div class="flex flex-col items-center">
<svg class="horse-icon" viewBox="0 0 100 60" xmlns="http://www.w3.org/2000/svg">
<path d="M15,45 C20,35 35,25 50,25 C65,25 80,30 90,20 C100,10 85,0 70,0 C55,0 45,10 40,20 C35,30 20,35 10,50 C7,55 15,60 30,60 L40,45" fill="#E67E22" />
<path d="M65,28 L70,40 M75,30 L80,42" stroke="#E67E22" stroke-width="4" stroke-linecap="round"/>
</svg>
<span class="text-2xl font-light text-teal-600 leading-none">{{ weather.temp }}°C</span>
<p class="text-[10px] text-gray-400 mt-1">當地氣溫</p>
</div>
</header>
<div class="px-4 mb-6">
<div class="flex overflow-x-auto hide-scrollbar space-x-3 py-2">
<button v-for="day in days" :key="day.id" @click="selectedDay = day.id"
:class="selectedDay === day.id ? 'active-day' : 'bg-white text-gray-400'"
class="flex-shrink-0 w-16 h-20 rounded-2xl shadow-sm flex flex-col items-center justify-center transition-all border border-gray-100">
<span class="text-[9px] font-bold uppercase">{{ day.city }}</span>
<span class="text-sm font-bold my-0.5">{{ day.date }}</span>
<span class="text-[10px] opacity-60">D{{ day.id }}</span>
</button>
</div>
</div>
<main class="px-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-sm font-bold text-gray-500 border-l-4 border-teal-500 pl-3">每日行程</h2>
<button @click="addItem" class="text-teal-600 text-xs font-bold">+ 新增行程</button>
</div>
<div class="space-y-4">
<div v-for="(item, index) in itinerary" :key="index">
<div v-if="item.day === selectedDay" class="flex gap-4">
<input v-model="item.time" class="text-[10px] font-mono text-teal-600 w-12 text-center bg-transparent focus:outline-none">
<div class="bg-white p-4 rounded-3xl shadow-sm flex-grow border border-gray-100">
<input v-model="item.location" class="w-full font-medium text-gray-700 focus:outline-none bg-transparent">
<div class="flex justify-between mt-2">
<span class="text-[10px] text-gray-400"><i class="fas fa-map-marker-alt mr-1"></i> 點擊編輯地點</span>
<button @click="deleteItem(index)" class="text-red-200 text-[10px]">刪除</button>
</div>
</div>
</div>
</div>
</div>
</main>
<nav class="fixed bottom-6 left-1/2 -translate-x-1/2 w-[80%] bg-white/90 backdrop-blur-md rounded-full shadow-2xl flex justify-around py-3 border border-white">
<button class="text-teal-600"><i class="fas fa-calendar-alt text-xl"></i></button>
<button class="text-gray-300"><i class="fas fa-wallet text-xl"></i></button>
</nav>
</div>
<script>
const { createApp, ref, computed, onMounted, watch } = Vue;
createApp({
setup() {
const selectedDay = ref(1);
const syncing = ref(false);
const weather = ref({ temp: '--' });
const itinerary = ref([
{ day: 1, time: "14:00", location: "抵達大阪 / 飯店 Check-in" },
{ day: 1, time: "18:30", location: "道頓堀吃晚餐" },
{ day: 2, time: "10:00", location: "大阪城公園" }
]);
const days = [];
const cities = ['大阪','大阪','大阪','京都','京都','京都','神戶','神戶','神戶','神戶','曼谷','曼谷','曼谷','曼谷','清邁','清邁','清邁','清邁','清邁','檳城','檳城','檳城','檳城','檳城','檳城'];
for(let i=1; i<=25; i++) {
days.push({ id: i, date: `1/${30+i}`, city: cities[i-1], lat: 34.6, lng: 135.5 });
}
const fetchWeather = async () => {
try {
const d = days[selectedDay.value-1];
const res = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=${d.lat}&longitude=${d.lng}&current_weather=true`);
const data = await res.json();
weather.value.temp = Math.round(data.current_weather.temperature);
} catch (e) { weather.value.temp = '25'; }
};
onMounted(fetchWeather);
watch(selectedDay, fetchWeather);
return {
selectedDay, days, itinerary, weather, syncing,
currentCity: computed(() => days[selectedDay.value-1].city),
currentRange: computed(() => days[selectedDay.value-1].date),
addItem: () => itinerary.value.push({ day: selectedDay.value, time: '12:00', location: '新地點' }),
deleteItem: (i) => itinerary.value.splice(i, 1),
syncCloud: () => { syncing.value = true; setTimeout(() => { syncing.value = false; alert('繁體版同步成功!'); }, 800); }
};
}
}).mount('#app');
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment