|
|
|
|
@ -0,0 +1,195 @@
|
|
|
|
|
// 导出可交互的 HTML 行程文件
|
|
|
|
|
export function exportToHtml(scheme, filename = null) {
|
|
|
|
|
if (!scheme) return
|
|
|
|
|
|
|
|
|
|
const html = generateHtml(scheme)
|
|
|
|
|
const blob = new Blob([html], { type: 'text/html;charset=utf-8' })
|
|
|
|
|
const url = URL.createObjectURL(blob)
|
|
|
|
|
const link = document.createElement('a')
|
|
|
|
|
link.href = url
|
|
|
|
|
link.download = filename || `${scheme.name || '行程规划'}.html`
|
|
|
|
|
document.body.appendChild(link)
|
|
|
|
|
link.click()
|
|
|
|
|
document.body.removeChild(link)
|
|
|
|
|
URL.revokeObjectURL(url)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function generateHtml(scheme) {
|
|
|
|
|
const daysDetail = scheme.daysDetail || []
|
|
|
|
|
const points = []
|
|
|
|
|
|
|
|
|
|
// 从 daysDetail 中提取站点信息
|
|
|
|
|
daysDetail.forEach((day, idx) => {
|
|
|
|
|
points.push({
|
|
|
|
|
name: day.location || `Day ${idx + 1}`,
|
|
|
|
|
day: `Day ${idx + 1}`,
|
|
|
|
|
desc: day.desc || '',
|
|
|
|
|
km: day.km || '—',
|
|
|
|
|
driveTime: day.driveTime || '—',
|
|
|
|
|
schedule: day.schedule || [],
|
|
|
|
|
foods: day.foods || [],
|
|
|
|
|
waypoints: day.waypoints || [],
|
|
|
|
|
hotel: day.hotel || ''
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return `<!DOCTYPE html>
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<title>${scheme.name || '行程规划'}</title>
|
|
|
|
|
<style>
|
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f5f7fa; color: #2d3436; line-height: 1.6; }
|
|
|
|
|
.container { max-width: 900px; margin: 0 auto; padding: 24px; }
|
|
|
|
|
.header { background: linear-gradient(135deg, #6c5ce7, #a29bfe); color: #fff; padding: 32px; border-radius: 16px; margin-bottom: 24px; }
|
|
|
|
|
.header h1 { font-size: 24px; margin-bottom: 12px; }
|
|
|
|
|
.meta { display: flex; gap: 16px; flex-wrap: wrap; font-size: 14px; opacity: 0.9; }
|
|
|
|
|
.meta span { background: rgba(255,255,255,0.2); padding: 4px 12px; border-radius: 6px; }
|
|
|
|
|
.highlights { background: #fff; padding: 20px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
|
|
|
|
|
.highlights h2 { font-size: 18px; margin-bottom: 12px; color: #6c5ce7; }
|
|
|
|
|
.highlights ul { padding-left: 20px; }
|
|
|
|
|
.highlights li { margin-bottom: 6px; }
|
|
|
|
|
.tabs { display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap; }
|
|
|
|
|
.tab { padding: 10px 20px; background: #fff; border: 2px solid #eee; border-radius: 8px; cursor: pointer; font-weight: 600; transition: all 0.2s; }
|
|
|
|
|
.tab:hover { border-color: #6c5ce7; color: #6c5ce7; }
|
|
|
|
|
.tab.active { background: #6c5ce7; color: #fff; border-color: #6c5ce7; }
|
|
|
|
|
.day-content { background: #fff; padding: 24px; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); display: none; }
|
|
|
|
|
.day-content.active { display: block; }
|
|
|
|
|
.day-header { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #eee; }
|
|
|
|
|
.day-badge { background: #6c5ce7; color: #fff; padding: 4px 12px; border-radius: 6px; font-size: 13px; font-weight: 600; }
|
|
|
|
|
.day-title { font-size: 18px; font-weight: 700; }
|
|
|
|
|
.day-km { margin-left: auto; font-size: 13px; color: #999; }
|
|
|
|
|
.schedule { margin-bottom: 20px; }
|
|
|
|
|
.schedule-item { display: flex; gap: 12px; margin-bottom: 12px; padding: 12px; background: #f8f9fa; border-radius: 8px; }
|
|
|
|
|
.schedule-time { font-size: 13px; font-weight: 600; color: #6c5ce7; min-width: 60px; }
|
|
|
|
|
.schedule-content { flex: 1; }
|
|
|
|
|
.schedule-content strong { display: block; margin-bottom: 4px; }
|
|
|
|
|
.schedule-content p { font-size: 14px; color: #636e72; margin: 0; }
|
|
|
|
|
.waypoints { margin-bottom: 20px; }
|
|
|
|
|
.waypoints h3 { font-size: 15px; margin-bottom: 10px; color: #636e72; }
|
|
|
|
|
.waypoint-list { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
|
|
|
.waypoint-tag { background: #fff; border: 1px solid #dfe6e9; padding: 6px 12px; border-radius: 8px; font-size: 13px; }
|
|
|
|
|
.foods { margin-bottom: 20px; }
|
|
|
|
|
.foods h3 { font-size: 15px; margin-bottom: 10px; color: #636e72; }
|
|
|
|
|
.food-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
|
|
|
|
|
.food-item { display: flex; align-items: center; gap: 8px; padding: 10px; background: #f8f9fa; border-radius: 8px; }
|
|
|
|
|
.food-icon { font-size: 20px; }
|
|
|
|
|
.hotel { background: #f0f4ff; padding: 14px; border-radius: 8px; border-left: 3px solid #6c5ce7; font-size: 14px; color: #636e72; }
|
|
|
|
|
.footer { text-align: center; margin-top: 32px; padding: 20px; color: #999; font-size: 12px; }
|
|
|
|
|
@media (max-width: 600px) {
|
|
|
|
|
.container { padding: 16px; }
|
|
|
|
|
.header { padding: 20px; }
|
|
|
|
|
.header h1 { font-size: 20px; }
|
|
|
|
|
.meta { flex-direction: column; gap: 8px; }
|
|
|
|
|
.tabs { gap: 6px; }
|
|
|
|
|
.tab { padding: 8px 14px; font-size: 13px; }
|
|
|
|
|
.day-content { padding: 16px; }
|
|
|
|
|
.food-list { grid-template-columns: 1fr; }
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<div class="container">
|
|
|
|
|
<div class="header">
|
|
|
|
|
<h1>${scheme.name || '行程规划'}</h1>
|
|
|
|
|
<div class="meta">
|
|
|
|
|
<span>📍 ${scheme.route || '—'}</span>
|
|
|
|
|
<span>📅 ${scheme.days || '—'}天</span>
|
|
|
|
|
<span>🚗 ~${scheme.totalKm || '—'}km</span>
|
|
|
|
|
<span>💰 ${scheme.budget || '—'}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
${scheme.highlights && scheme.highlights.length > 0 ? `
|
|
|
|
|
<div class="highlights">
|
|
|
|
|
<h2>✨ 行程亮点</h2>
|
|
|
|
|
<ul>
|
|
|
|
|
${scheme.highlights.map(h => `<li>${h}</li>`).join('')}
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
` : ''}
|
|
|
|
|
|
|
|
|
|
<div class="tabs">
|
|
|
|
|
${points.map((p, i) => `
|
|
|
|
|
<div class="tab ${i === 0 ? 'active' : ''}" onclick="showDay(${i})">Day ${i + 1}</div>
|
|
|
|
|
`).join('')}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
${points.map((p, i) => `
|
|
|
|
|
<div class="day-content ${i === 0 ? 'active' : ''}" id="day-${i}">
|
|
|
|
|
<div class="day-header">
|
|
|
|
|
<span class="day-badge">Day ${i + 1}</span>
|
|
|
|
|
<span class="day-title">${p.name}</span>
|
|
|
|
|
<span class="day-km">${p.km}km · 驾车${p.driveTime}h</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
${p.desc ? `<p style="margin-bottom:16px;color:#636e72;">${p.desc}</p>` : ''}
|
|
|
|
|
|
|
|
|
|
${p.schedule && p.schedule.length > 0 ? `
|
|
|
|
|
<div class="schedule">
|
|
|
|
|
<h3 style="margin-bottom:12px;">📅 日程安排</h3>
|
|
|
|
|
${p.schedule.map(s => `
|
|
|
|
|
<div class="schedule-item">
|
|
|
|
|
<div class="schedule-time">${s.time || '—'}</div>
|
|
|
|
|
<div class="schedule-content">
|
|
|
|
|
<strong>${s.title || s.content || '—'}</strong>
|
|
|
|
|
<p>${s.desc || ''}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`).join('')}
|
|
|
|
|
</div>
|
|
|
|
|
` : ''}
|
|
|
|
|
|
|
|
|
|
${p.waypoints && p.waypoints.length > 0 ? `
|
|
|
|
|
<div class="waypoints">
|
|
|
|
|
<h3>🗺️ 途径推荐</h3>
|
|
|
|
|
<div class="waypoint-list">
|
|
|
|
|
${p.waypoints.map(wp => `
|
|
|
|
|
<div class="waypoint-tag">${wp.icon || '📍'} ${wp.name}</div>
|
|
|
|
|
`).join('')}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
` : ''}
|
|
|
|
|
|
|
|
|
|
${p.foods && p.foods.length > 0 ? `
|
|
|
|
|
<div class="foods">
|
|
|
|
|
<h3>🍽️ 美食推荐</h3>
|
|
|
|
|
<div class="food-list">
|
|
|
|
|
${p.foods.map(f => {
|
|
|
|
|
const name = typeof f === 'string' ? f : (f.name || f)
|
|
|
|
|
const icon = typeof f === 'object' ? (f.icon || '🍜') : '🍜'
|
|
|
|
|
return `<div class="food-item"><span class="food-icon">${icon}</span><span>${name}</span></div>`
|
|
|
|
|
}).join('')}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
` : ''}
|
|
|
|
|
|
|
|
|
|
${p.hotel ? `
|
|
|
|
|
<div class="hotel">🏨 住宿推荐:${p.hotel}</div>
|
|
|
|
|
` : ''}
|
|
|
|
|
</div>
|
|
|
|
|
`).join('')}
|
|
|
|
|
|
|
|
|
|
${scheme.tips ? `
|
|
|
|
|
<div style="background:#fff8e1;padding:20px;border-radius:12px;margin-top:20px;border-left:3px solid #f39c12;">
|
|
|
|
|
<h3 style="margin-bottom:8px;">💡 旅行贴士</h3>
|
|
|
|
|
<p>${scheme.tips}</p>
|
|
|
|
|
</div>
|
|
|
|
|
` : ''}
|
|
|
|
|
|
|
|
|
|
<div class="footer">由智能行程规划系统生成</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
function showDay(idx) {
|
|
|
|
|
document.querySelectorAll('.day-content').forEach(el => el.classList.remove('active'));
|
|
|
|
|
document.querySelectorAll('.tab').forEach(el => el.classList.remove('active'));
|
|
|
|
|
document.getElementById('day-' + idx).classList.add('active');
|
|
|
|
|
document.querySelectorAll('.tab')[idx].classList.add('active');
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>`
|
|
|
|
|
}
|