用脚步丈量世界 · 用心记录旅行

发现城市角落,记录每一次探店之旅

城市 美食 自然 人文 短途
🗺️

查地图

通过互动地图,发现身边的宝藏店铺和旅行目的地,规划你的专属路线

📋

看规划

智能行程规划工具,轻松安排每日行程,让你的旅行更有条理

⭐

看评价

真实用户探店分享,为你提供最靠谱的旅行参考和美食推荐

足迹地图

探索城市中的优质店铺,发现隐藏的宝藏之地

全部 🏙️ 城市旅行 🍜 美食探店 🏔️ 自然风光 🏛️ 历史人文 🚶 短途漫游
图例
城市旅行
美食探店
自然风光
历史人文
短途漫游

热门游记

精选探店故事,总有你喜欢的旅行方式

用户旅途足迹 · 记录出行见闻

全部 城市旅行 美食探店 自然风光 历史人文 短途漫游

足迹工具

行程规划与灵感探索

📋 可视化行程规划器 💡 灵感规划库 📁 我的行程 📝 灵感投稿

行程时间轴

共 0 个途经点 总距离 0 km

📝 灵感投稿

分享你的行程灵感,让更多人发现精彩路线

建议包含行程天数、推荐店铺、特色体验等
添加具体途经点,包含名称、地址、时间等信息
用逗号分隔标签,方便其他人搜索发现
支持同时选择多张图片,第一张将作为封面展示,点击下方图片可放大查看
🎉

投稿成功!已同步到灵感规划库

你的行程灵感已提交,其他人可以看到并参考你的路线

行程标题

👤 匿名用户
📍 目的地 ⏱ 行程天数 📅 日期
行程描述...

关于探店足迹

我们相信,每一次探店都是一次发现。不追求华丽的包装,只在乎真实的记录。探店足迹致力于为旅行爱好者提供一个纯净、自由的分享平台——用文字记录城市角落的故事,用脚步丈量每一寸土地的温度。无论是隐藏在小巷里的咖啡馆,还是城市边缘的风景线,每一种探索都值得被铭记。

🗺️

足迹地图

可视化你的旅行足迹,在地图上标记每一处值得回忆的地点,让旅途有迹可循

📝

游记分享

用图文记录你的探店故事,与志同道合的旅行者交流心得,分享旅途中的美好瞬间

🧭

行程规划

智能规划你的旅行路线,灵活安排每日行程,让每一次出行都从容不迫

规划器功能一览

🍜

美食

精选当地美食推荐

🏛️

景点

热门景点一站收录

🗺️

路线

自定义路线灵活编排

🚌

交通

智能交通路线规划

⏰

时间

合理分配每段行程

📋

一键复制

灵感行程一键获取

📧 联系我们:hello@tan-dian.com

期待你的每一次分享,让探店足迹陪伴你走过更多城市

留言反馈

分享你的使用体验,帮助我们做得更好

我的发布 我的评论 消息通知 我的留言
'; var blob = new Blob(['\ufeff' + content], { type: 'application/msword' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = '行程规划_' + new Date().toLocaleDateString('zh-CN').replace(/\//g, '-') + '.doc'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); document.getElementById('downloadMenu').style.display = 'none'; showToast('Word 下载完成!'); } // Close download menu when clicking outside document.addEventListener('click', function(e) { var menu = document.getElementById('downloadMenu'); if (menu && menu.style.display === 'block') { if (!e.target.closest('.download-menu-wrap')) { menu.style.display = 'none'; } } }); function navigateAndOpenDetail(id) { navigateTo('travel'); setTimeout(function() { openNoteDetail(id); }, 150); } function updateNavState(page) { document.querySelectorAll('.nav-links a').forEach(function(a) { a.classList.remove('active'); }); var navLink = document.querySelector('.nav-links a[data-page="' + page + '"]'); if (navLink) navLink.classList.add('active'); } function navigateTo(page) { document.getElementById('detailOverlay').classList.remove('active'); document.querySelector('.nav').classList.remove('hidden'); currentDetailNote = null; var activePage = document.querySelector('.page.active'); if (activePage) { var activeId = activePage.id.replace('page-', ''); if (activeId !== 'mine') { previousPage = activeId; } } document.querySelectorAll('.page').forEach(function(p) { p.classList.remove('active'); }); var pageEl = document.getElementById('page-' + page); if (pageEl) pageEl.classList.add('active'); updateNavState(page); window.scrollTo(0, 0); if (page === 'home') { setTimeout(function() { if (!homeMap) initHomeMap(); }, 100); } if (page === 'travel') { renderNotes(); renderSocialFeed(); } if (page === 'tools') { renderStops(); renderInspirationRoutes(); renderMyRoutes(); updateSavedBadge(); setTimeout(function() { if (!plannerMap) initPlannerMap(); }, 100); } if (page === 'mine') { if (!currentUser) { openLoginModal('mine'); return; } updateMineHeader(); switchMineTab('posts'); } if (page === 'feedback') { renderFeedbackList(); } history.replaceState(null, '', location.pathname + '#' + page); } var navigate = navigateTo; function renderNotes() { var grid = document.getElementById('notesGrid'); var notes = currentFilter === '全部' ? travelNotes : travelNotes.filter(function(n) { return n.tag === currentFilter; }); var html = ''; for (var i = 0; i < notes.length; i++) { var note = notes[i]; html += '
'; html += '
'; html += '
'; html += '' + note.tag + ''; html += '❤ ' + note.likes + ''; html += '
'; html += '
'; html += '

' + note.title + '

'; html += '
'; html += '
' + note.author.charAt(0) + '
'; html += '' + note.author + '📍 ' + note.location + ''; html += '
'; html += '
' + note.summary + '
'; html += '
'; } grid.innerHTML = html || '

暂无该分类的游记

'; } function filterNotes(tag) { currentFilter = tag; document.querySelectorAll('.filter-tab').forEach(function(t) { t.classList.remove('active'); }); var filterTab = document.querySelector('.filter-tab[data-filter="' + tag + '"]'); if (filterTab) filterTab.classList.add('active'); renderNotes(); } function renderHomeStories() { var container = document.getElementById('homeStoryCards'); var stories = travelNotes.slice(0, 4); var html = ''; for (var i = 0; i < stories.length; i++) { var s = stories[i]; html += '
'; html += '
'; html += '
'; html += ''; html += '
'; html += '
'; html += '

' + s.title + '

'; html += '
'; html += '
' + s.author.charAt(0) + '
'; html += '' + s.author + ''; html += '📍 ' + s.location + ''; html += '
'; } container.innerHTML = html; } function openNoteDetail(id) { var note = travelNotes.find(function(n) { return n.id === id; }); if (!note) return; currentDetailNote = note; currentSlide = 0; detailLiked = false; commentImages = []; document.getElementById('page-travel').classList.remove('active'); document.getElementById('detailOverlay').classList.add('active'); document.querySelector('.nav').classList.add('hidden'); window.scrollTo(0, 0); renderNoteDetail(id); } function closeNoteDetail() { document.getElementById('detailOverlay').classList.remove('active'); document.querySelector('.nav').classList.remove('hidden'); currentDetailNote = null; navigateTo('travel'); } function renderNoteDetail(id) { if (id) { currentDetailNote = travelNotes.find(function(n) { return n.id === id; }); } var note = currentDetailNote; if (!note) return; var container = document.getElementById('detailContent'); var isAuthor = currentUser && currentUser === note.author; var imagesHtml = ''; for (var i = 0; i < note.images.length; i++) { imagesHtml += ''; } var dotsHtml = ''; for (var i = 0; i < note.images.length; i++) { dotsHtml += ''; } var commentsHtml = ''; if (currentUser) { commentsHtml += '
'; } else { commentsHtml += '
登录后即可发表评论
'; } for (var i = 0; i < note.comments.length; i++) { var c = note.comments[i]; commentsHtml += '
'; commentsHtml += '
' + c.author.charAt(0) + '
'; commentsHtml += '
'; commentsHtml += '
' + c.author + '
'; commentsHtml += '
' + c.text + '
'; if (c.images && c.images.length > 0) { commentsHtml += '
'; for (var j = 0; j < c.images.length; j++) { commentsHtml += '
'; } commentsHtml += '
'; } commentsHtml += '
'; } var relatedNotes = travelNotes.filter(function(n) { return n.id !== note.id; }).slice(0, 3); var relatedHtml = ''; for (var i = 0; i < relatedNotes.length; i++) { var r = relatedNotes[i]; relatedHtml += ''; } var html = ''; html += ''; html += '
'; html += '

' + note.title + '

'; html += '
'; html += '
' + note.author.charAt(0) + '
'; html += '' + note.author + ''; html += '📍 ' + note.location + ''; html += '🕐 ' + note.time + ''; html += '' + note.tag + ''; html += '
'; html += '
' + note.content + '
'; html += '
'; html += ''; html += ''; if (isAuthor) { html += ''; html += ''; } html += '
'; html += '
'; html += '

评论 (' + note.comments.length + ')

'; html += commentsHtml; html += '
'; html += '
'; container.innerHTML = html; } function goToSlide(index) { currentSlide = index; var cn = document.querySelector('.carousel-inner'); if (cn) cn.style.transform = 'translateX(-' + (index * 100) + '%)'; var dots = document.querySelectorAll('.carousel-dot'); dots.forEach(function(d, i) { d.classList.toggle('active', i === index); }); } function prevSlide() { var note = currentDetailNote; if (!note) return; currentSlide = (currentSlide - 1 + note.images.length) % note.images.length; goToSlide(currentSlide); } function nextSlide() { var note = currentDetailNote; if (!note) return; currentSlide = (currentSlide + 1) % note.images.length; goToSlide(currentSlide); } function toggleLike() { if (!currentDetailNote) return; detailLiked = !detailLiked; currentDetailNote.likes += detailLiked ? 1 : -1; var btn = document.getElementById('detailLikeBtn'); if (btn) { btn.innerHTML = (detailLiked ? '❤' : '🤍') + ' 喜欢 (' + currentDetailNote.likes + ')'; btn.classList.toggle('liked', detailLiked); } saveData(); } function shareNote() { if (currentDetailNote) { showShareModal(currentDetailNote.id); } } function editNote() { if (!currentDetailNote) return; editNoteById(currentDetailNote.id); } function editNoteById(id) { var note = travelNotes.find(function(n) { return n.id === id; }); if (!note) return; editingNoteId = id; document.getElementById('publishTitle').value = note.title; document.getElementById('publishLocation').value = note.location; document.getElementById('publishCategory').value = note.tag; document.getElementById('publishSummary').value = note.summary || ''; document.getElementById('publishContent').value = note.content.replace(/<\/?p>/g, '').replace(/
/g, '\n'); document.getElementById('publishDateStart').value = note.time; document.getElementById('publishDateEnd').value = note.dateEnd || ''; document.getElementById('publishTagsInput').value = (note.tags || []).join(','); publishTags = (note.tags || []).slice(); publishImages = []; document.getElementById('publishImageGrid').innerHTML = ''; var wordCount = (note.content || note.summary || '').length; document.getElementById('publishWordCount').textContent = wordCount + ' / 1000 字'; document.getElementById('publishModal').classList.add('active'); renderPublishTags(); } function deleteNoteById(id) { if (!confirm('确定删除这篇游记吗?')) return; travelNotes = travelNotes.filter(function(n) { return n.id !== id; }); saveData(); renderNotes(); renderSocialFeed(); renderMinePosts(); updateMineHeader(); showToast('游记已删除(评论已保留)'); } function deleteSocialPost(id) { if (!confirm('确定删除这条动态吗?')) return; socialPosts = socialPosts.filter(function(p) { return p.id !== id; }); saveData(); renderSocialFeed(); renderMinePosts(); updateMineHeader(); showToast('动态已删除'); } function deleteNote() { if (!currentDetailNote) return; if (confirm('确定删除这篇游记吗?')) { travelNotes = travelNotes.filter(function(n) { return n.id !== currentDetailNote.id; }); closeNoteDetail(); renderNotes(); renderSocialFeed(); saveData(); showToast('游记已删除(评论已保留)'); } } function handleCommentImageSelect(event) { var files = event.target.files; for (var i = 0; i < files.length; i++) { if (!files[i].type.match(/^image\//)) continue; if (commentImages.length >= 4) { showToast('最多上传 4 张图片'); break; } commentImages.push(files[i]); } event.target.value = ''; renderCommentImagePreview(); } function removeCommentImage(index) { commentImages.splice(index, 1); renderCommentImagePreview(); } function renderCommentImagePreview() { var grid = document.getElementById('commentImagePreview'); if (!grid) return; if (commentImages.length === 0) { grid.innerHTML = ''; grid.style.display = 'none'; return; } grid.style.display = 'flex'; var html = ''; for (var j = 0; j < commentImages.length; j++) { html += '
' + '' + '
'; } grid.innerHTML = html; for (var k = 0; k < commentImages.length; k++) { (function(idx) { var reader = new FileReader(); reader.onload = function(e) { var thumb = document.getElementById('ci-' + idx); if (thumb) thumb.style.backgroundImage = 'url(' + e.target.result + ')'; }; reader.readAsDataURL(commentImages[idx]); })(k); } } function submitComment() { if (!currentUser || !currentDetailNote) return; var input = document.getElementById('commentInput'); if (!input || !input.value.trim()) { if (commentImages.length === 0) return; } var createComment = function(imageUrls) { var newComment = { id: Date.now(), author: currentUser, text: input ? input.value.trim() : '', time: new Date().toISOString().slice(0, 10), likes: 0, liked: false, images: imageUrls }; currentDetailNote.comments.push(newComment); if (input) input.value = ''; commentImages = []; renderCommentImagePreview(); renderNoteDetail(); saveData(); }; if (commentImages.length > 0) { var urls = []; var loaded = 0; for (var i = 0; i < commentImages.length; i++) { var reader = new FileReader(); reader.onload = (function(idx) { return function(e) { urls[idx] = e.target.result; loaded++; if (loaded === commentImages.length) createComment(urls); }; })(i); reader.readAsDataURL(commentImages[i]); } } else { createComment([]); } } function likeComment(commentId) { if (!currentDetailNote) return; var comment = currentDetailNote.comments.find(function(c) { return c.id === commentId; }); if (!comment) return; if (!comment.liked) { comment.likes++; comment.liked = true; } else { comment.likes--; comment.liked = false; } renderNoteDetail(); saveData(); } function viewCommentImage(commentId, index) { if (!currentDetailNote) return; var comment = currentDetailNote.comments.find(function(c) { return c.id === commentId; }); if (!comment || !comment.images || comment.images.length === 0) return; openLightbox(comment.images, index); } function doLogout() { currentUser = null; document.getElementById('navUsername').style.display = 'none'; document.getElementById('navAvatar').style.display = 'none'; document.getElementById('navLoginBtn').style.display = 'inline'; document.getElementById('navLogoutBtn').style.display = 'none'; saveData(); showToast('已退出登录'); updateSubmissionLoginState(); if (previousPage) { navigateTo(previousPage); previousPage = null; } } function switchMineTab(tab) { document.querySelectorAll('.mine-tab').forEach(function(t) { t.classList.remove('active'); }); document.querySelectorAll('.mine-panel').forEach(function(p) { p.classList.remove('active'); }); var tabEl = document.querySelector('.mine-tab[data-minetab="' + tab + '"]'); if (tabEl) tabEl.classList.add('active'); var panel = document.getElementById('minePanel-' + tab); if (panel) panel.classList.add('active'); if (tab === 'posts') renderMinePosts(); else if (tab === 'comments') renderMineComments(); else if (tab === 'notifications') renderMineNotifications(); else if (tab === 'feedbacks') renderMineFeedbacks(); } function triggerAvatarUpload() { document.getElementById('avatarInput').click(); } function handleAvatarChange(event) { var file = event.target.files[0]; if (!file) return; if (!file.type.startsWith('image/')) { showToast('请选择图片文件'); return; } if (file.size > 5 * 1024 * 1024) { showToast('图片大小不能超过5MB'); return; } var reader = new FileReader(); reader.onload = function(e) { userAvatar = e.target.result; saveData(); updateMineHeader(); updateNavAvatar(); showToast('头像更新成功'); }; reader.readAsDataURL(file); } function updateNavAvatar() { var navEl = document.getElementById('navAvatar'); if (!navEl) return; if (userAvatar) { navEl.style.backgroundImage = 'url(' + userAvatar + ')'; navEl.classList.add('has-image'); navEl.textContent = ''; } else { navEl.style.backgroundImage = ''; navEl.classList.remove('has-image'); if (currentUser) navEl.textContent = currentUser.charAt(0); } } function updateMineHeader() { var avatarEl = document.getElementById('mineAvatar'); var usernameEl = document.getElementById('mineUsername'); var statsEl = document.getElementById('mineStats'); if (!avatarEl || !usernameEl || !statsEl) return; if (!currentUser) { avatarEl.style.backgroundImage = ''; avatarEl.classList.remove('has-image'); avatarEl.textContent = ''; usernameEl.textContent = ''; statsEl.innerHTML = ''; return; } if (userAvatar) { avatarEl.style.backgroundImage = 'url(' + userAvatar + ')'; avatarEl.classList.add('has-image'); avatarEl.textContent = ''; } else { avatarEl.style.backgroundImage = ''; avatarEl.classList.remove('has-image'); avatarEl.textContent = currentUser.charAt(0); } usernameEl.textContent = currentUser; var myPosts = travelNotes.filter(function(n) { return n.author === currentUser; }); var mySocial = socialPosts.filter(function(p) { return p.userName === currentUser; }); var totalPosts = myPosts.length + mySocial.length; var commentCount = getMyCommentCount(); var feedbackCount = getFeedbacks().filter(function(f) { return f.user === currentUser; }).length; statsEl.innerHTML = '' + totalPosts + ' 笔记' + commentCount + ' 评论' + feedbackCount + ' 留言'; } function getMyCommentCount() { var count = 0; for (var i = 0; i < travelNotes.length; i++) { for (var j = 0; j < travelNotes[i].comments.length; j++) { if (travelNotes[i].comments[j].author === currentUser) count++; } } for (var i = 0; i < socialPosts.length; i++) { for (var j = 0; j < socialPosts[i].comments.length; j++) { var c = socialPosts[i].comments[j]; if (c.user === currentUser || c.username === currentUser) count++; } } return count; } function renderMinePosts() { var panel = document.getElementById('minePanel-posts'); if (!panel) return; var myPosts = travelNotes.filter(function(n) { return n.author === currentUser; }); var mySocialPosts = socialPosts.filter(function(p) { return p.userName === currentUser; }); if (myPosts.length === 0 && mySocialPosts.length === 0) { panel.innerHTML = '
你还没有发布过游记
'; return; } var html = ''; for (var i = 0; i < myPosts.length; i++) { var n = myPosts[i]; html += '
'; html += '
'; html += '

' + n.title + '

📍 ' + n.location + ' · ' + n.time + ' · ❤ ' + n.likes + '
'; html += '
'; html += ''; html += ''; html += ''; html += '
'; html += '
'; } for (var j = 0; j < mySocialPosts.length; j++) { var sp = mySocialPosts[j]; html += '
'; html += '
'; html += '

动态 · ' + sp.location + '

' + sp.time + ' · ❤ ' + sp.likes + ' · 💬 ' + sp.comments.length + '条评论
'; html += '
'; html += '
'; } panel.innerHTML = html; } function renderMineComments() { var panel = document.getElementById('minePanel-comments'); if (!panel) return; var myComments = []; for (var i = 0; i < travelNotes.length; i++) { var n = travelNotes[i]; for (var j = 0; j < n.comments.length; j++) { if (n.comments[j].author === currentUser) { myComments.push({ postTitle: n.title || '一篇游记', comment: n.comments[j], source: 'note', noteId: n.id, commentId: n.comments[j].id }); } } } for (var i = 0; i < socialPosts.length; i++) { var p = socialPosts[i]; for (var j = 0; j < p.comments.length; j++) { var c = p.comments[j]; if (c.user === currentUser || c.username === currentUser) { myComments.push({ post: p, comment: c, source: 'social', postId: p.id, commentId: c.id }); } } } if (myComments.length === 0) { panel.innerHTML = '
你还没有发表过评论
'; return; } var html = ''; for (var k = 0; k < myComments.length; k++) { var mc = myComments[k]; var commentText = mc.comment.text || mc.comment.content || ''; html += '
'; if (mc.source === 'note') { html += '
评论了游记:' + mc.postTitle + '
'; } else { html += '
回复了 ' + mc.post.userName + ' 的动态
'; } html += '
' + commentText + '
'; html += '
' + mc.comment.time + '
'; if (mc.source === 'note') { html += '
'; } else { html += '
'; } html += '
'; } panel.innerHTML = html; } function deleteMineComment(source, id1, id2) { if (!confirm('确定删除这条评论吗?')) return; if (source === 'social') { var post = socialPosts.find(function(p) { return p.id === id1; }); if (!post) return; post.comments = post.comments.filter(function(c) { return c.id !== id2; }); } else if (source === 'note') { var note = travelNotes.find(function(n) { return n.id === id1; }); if (!note) return; note.comments = note.comments.filter(function(c) { return c.id !== id2; }); } saveData(); renderMineComments(); updateMineHeader(); showToast('评论已删除'); } function renderMineNotifications() { var panel = document.getElementById('minePanel-notifications'); if (!panel) return; if (notifications.length === 0) { panel.innerHTML = '
暂无消息通知
'; return; } var html = ''; for (var i = 0; i < notifications.length; i++) { var n = notifications[i]; html += '
'; html += '
'; html += '
' + n.user + ' 评论了你的动态:' + n.text + '
'; html += '
' + n.time + '
'; html += '
'; } panel.innerHTML = html; } function renderMineFeedbacks() { var panel = document.getElementById('minePanel-feedbacks'); if (!panel) return; var myFeedbacks = getFeedbacks().filter(function(f) { return f.user === currentUser; }); if (myFeedbacks.length === 0) { panel.innerHTML = '
💬
你还没有提交过留言
'; return; } var typeLabels = { suggestion: '功能建议', bug: '问题反馈', review: '使用评价', other: '其他' }; var typeIcons = { suggestion: '💡', bug: '🐛', review: '⭐', other: '💬' }; var html = ''; for (var i = 0; i < myFeedbacks.length; i++) { var fb = myFeedbacks[i]; var starsHtml = ''; if (fb.type === 'review') { for (var j = 1; j <= 5; j++) { starsHtml += '★'; } } html += '
' + '
' + '' + (typeIcons[fb.type] || '💬') + '' + '' + (fb.type === 'review' ? '' + starsHtml + '' : '') + '' + fb.date + '' + '' + '
' + '
' + escapeHtml(fb.content) + '
' + '
'; } panel.innerHTML = html; } function markNotifRead(id) { var n = notifications.find(function(x) { return x.id === id; }); if (n) { n.read = true; } renderMineNotifications(); saveData(); } var loginFrom = null; var previousPage = null; function openLoginModal(from) { loginFrom = from || null; document.getElementById('loginModal').classList.add('active'); } function closeLoginModal() { loginFrom = null; document.getElementById('loginModal').classList.remove('active'); } function doLogin() { var phone = document.getElementById('loginPhone').value.trim(); var pass = document.getElementById('loginPass').value.trim(); var errEl = document.getElementById('loginError'); errEl.style.display = 'none'; if (!phone) { errEl.textContent = '请输入手机号'; errEl.style.display = 'block'; return; } if (!pass) { showToast('请输入密码'); return; } if (phone.length !== 11 || phone.charAt(0) !== '1') { errEl.textContent = '用户不存在,请先注册'; errEl.style.display = 'block'; return; } var users = JSON.parse(localStorage.getItem('tandian_users') || '{}'); if (!users[phone]) { errEl.textContent = '用户不存在,请先注册'; errEl.style.display = 'block'; return; } if (users[phone] !== pass) { showToast('密码错误'); return; } loginSuccess(phone); } function clearLoginError() { var errEl = document.getElementById('loginError'); if (errEl) { errEl.style.display = 'none'; errEl.textContent = ''; } } function clearRegError() { var errEl = document.getElementById('regError'); if (errEl) { errEl.style.display = 'none'; errEl.textContent = ''; } } function loginSuccess(phone) { var errEl = document.getElementById('loginError'); if (errEl) { errEl.style.display = 'none'; errEl.textContent = ''; } currentUser = '用户' + phone.slice(-4); document.getElementById('navUsername').textContent = currentUser; document.getElementById('navUsername').style.display = 'inline'; document.getElementById('navAvatar').textContent = currentUser.charAt(0); document.getElementById('navAvatar').style.display = 'flex'; document.getElementById('navLoginBtn').style.display = 'none'; document.getElementById('navLogoutBtn').style.display = 'inline'; var from = loginFrom; closeLoginModal(); saveData(); showToast('欢迎回来,' + currentUser + '!'); if (from === 'comment') { renderSocialFeed(); setTimeout(function() { var inputs = document.querySelectorAll('.social-comment-input input'); if (inputs.length > 0) inputs[0].focus(); }, 200); } if (from === 'mine') { navigateTo('mine'); } if (from === 'publish') { openPublishModal(); } if (from === 'submission') { switchToolsTab('submission'); updateSubmissionLoginState(); } if (currentDetailNote) { renderNoteDetail(); } } function showRegisterForm() { clearLoginError(); document.getElementById('loginForm').style.display = 'none'; document.getElementById('registerForm').style.display = 'block'; } function showLoginForm() { clearRegError(); document.getElementById('registerForm').style.display = 'none'; document.getElementById('loginForm').style.display = 'block'; } function doRegister() { var phone = document.getElementById('regPhone').value.trim(); var pass = document.getElementById('regPass').value.trim(); var passConfirm = document.getElementById('regPassConfirm').value.trim(); var errEl = document.getElementById('regError'); errEl.style.display = 'none'; if (!phone) { errEl.textContent = '请输入手机号'; errEl.style.display = 'block'; return; } if (phone.length !== 11 || phone.charAt(0) !== '1') { errEl.textContent = '请输入正确的11位手机号'; errEl.style.display = 'block'; return; } if (!pass || pass.length < 6) { showToast('密码至少6位'); return; } if (pass !== passConfirm) { showToast('两次密码不一致'); return; } var users = JSON.parse(localStorage.getItem('tandian_users') || '{}'); if (users[phone]) { errEl.textContent = '该手机号已注册,请直接登录'; errEl.style.display = 'block'; return; } users[phone] = pass; localStorage.setItem('tandian_users', JSON.stringify(users)); showToast('注册成功!'); document.getElementById('regPhone').value = ''; document.getElementById('regPass').value = ''; document.getElementById('regPassConfirm').value = ''; showLoginForm(); document.getElementById('loginPhone').value = phone; } function openPublishModal() { if (!currentUser) { openLoginModal('publish'); return; } resetPublishForm(); document.getElementById('publishModal').classList.add('active'); } function closePublishModal() { document.getElementById('publishModal').classList.remove('active'); resetPublishForm(); } function resetPublishForm() { editingNoteId = null; document.getElementById('publishTitle').value = ''; document.getElementById('publishLocation').value = ''; document.getElementById('publishSummary').value = ''; document.getElementById('publishContent').value = ''; document.getElementById('publishDateStart').value = ''; document.getElementById('publishDateEnd').value = ''; document.getElementById('publishTagsInput').value = ''; publishImages = []; publishTags = []; document.getElementById('publishImageGrid').innerHTML = ''; document.getElementById('publishTagsContainer').innerHTML = ''; document.getElementById('publishImageUpload').classList.remove('has-images'); document.getElementById('publishWordCount').textContent = '0 / 1000 字'; document.getElementById('publishWordCount').className = 'word-count'; document.getElementById('publishSubmitBtn').classList.remove('loading'); document.getElementById('publishSubmitBtn').disabled = false; document.getElementById('pubTitleError').classList.remove('show'); document.getElementById('pubContentError').classList.remove('show'); document.getElementById('pubImageError').classList.remove('show'); document.getElementById('pubTitleGroup').classList.remove('error'); document.getElementById('pubContentGroup').classList.remove('error'); document.getElementById('locationDropdown').classList.remove('show'); } function publishNote() { var title = document.getElementById('publishTitle').value.trim(); var location = document.getElementById('publishLocation').value.trim(); var category = document.getElementById('publishCategory').value; var summary = document.getElementById('publishSummary').value.trim(); var content = document.getElementById('publishContent').value.trim(); var dateStart = document.getElementById('publishDateStart').value; var dateEnd = document.getElementById('publishDateEnd').value; var hasError = false; document.getElementById('pubTitleError').classList.remove('show'); document.getElementById('pubContentError').classList.remove('show'); document.getElementById('pubImageError').classList.remove('show'); document.getElementById('pubTitleGroup').classList.remove('error'); document.getElementById('pubContentGroup').classList.remove('error'); if (!title) { document.getElementById('pubTitleError').classList.add('show'); document.getElementById('pubTitleGroup').classList.add('error'); hasError = true; } if (!content) { document.getElementById('pubContentError').classList.add('show'); document.getElementById('pubContentGroup').classList.add('error'); hasError = true; } if (publishImages.length === 0 && !editingNoteId) { document.getElementById('pubImageError').classList.add('show'); hasError = true; } if (hasError) { showToast('请填写必填字段并上传图片'); return; } var btn = document.getElementById('publishSubmitBtn'); btn.classList.add('loading'); btn.disabled = true; if (editingNoteId) { var editNote = travelNotes.find(function(n) { return n.id === editingNoteId; }); if (!editNote) { showToast('编辑失败'); btn.classList.remove('loading'); btn.disabled = false; return; } editNote.title = title; editNote.location = location; editNote.tag = category; editNote.tags = publishTags.slice(); editNote.summary = summary; editNote.content = '

' + content.replace(/\n/g, '

') + '

'; editNote.time = dateStart || editNote.time; editNote.dateEnd = dateEnd || ''; if (publishImages.length > 0) { var procEditImgs = function(callback) { var urls = []; var ld = 0; for (var i = 0; i < publishImages.length; i++) { var reader = new FileReader(); reader.onload = (function(idx) { return function(e) { urls[idx] = e.target.result; ld++; if (ld === publishImages.length) callback(urls); }; })(i); reader.readAsDataURL(publishImages[i]); } }; procEditImgs(function(urls) { editNote.images = urls; editNote.cover = urls[0]; editingNoteId = null; closePublishModal(); renderNotes(); renderSocialFeed(); renderMinePosts(); updateMineHeader(); saveData(); showToast('游记已更新'); }); } else { editingNoteId = null; closePublishModal(); renderNotes(); renderSocialFeed(); renderMinePosts(); updateMineHeader(); saveData(); showToast('游记已更新'); } return; } var processImages = function(callback) { var imageUrls = []; var loaded = 0; if (publishImages.length === 0) { callback([]); return; } for (var i = 0; i < publishImages.length; i++) { var reader = new FileReader(); reader.onload = (function(idx) { return function(e) { imageUrls[idx] = e.target.result; loaded++; if (loaded === publishImages.length) callback(imageUrls); }; })(i); reader.readAsDataURL(publishImages[i]); } }; processImages(function(imageUrls) { var gradients = [ 'linear-gradient(135deg, #e74c3c, #c0392b)', 'linear-gradient(135deg, #5b8c5a, #3d6b3c)', 'linear-gradient(135deg, #8b7355, #6b5b4a)', 'linear-gradient(135deg, #d4a574, #c4956a)', 'linear-gradient(135deg, #4a8fa8, #3a7a98)' ]; var cover = imageUrls.length > 0 ? imageUrls[0] : gradients[Math.floor(Math.random() * gradients.length)]; var newNote = { id: Date.now(), title: title, author: currentUser, location: location, time: dateStart || new Date().toISOString().slice(0, 10), dateEnd: dateEnd || '', tag: category, tags: publishTags.slice(), summary: summary, cover: cover, likes: 0, content: '

' + content.replace(/\n/g, '

') + '

', comments: [], images: imageUrls }; travelNotes.unshift(newNote); closePublishModal(); renderNotes(); saveData(); showToast('游记发布成功!'); }); } function handlePublishImageSelect(event) { var files = event.target.files; for (var i = 0; i < files.length; i++) { if (!files[i].type.match(/^image\//)) continue; if (publishImages.length >= 9) { showToast('最多上传 9 张图片'); break; } publishImages.push(files[i]); } event.target.value = ''; renderPublishImageGrid(); } function renderPublishImageGrid() { var grid = document.getElementById('publishImageGrid'); var upload = document.getElementById('publishImageUpload'); var html = ''; for (var i = 0; i < publishImages.length; i++) { var url = URL.createObjectURL(publishImages[i]); html += '
'; html += ''; html += '' + (i + 1) + '/' + publishImages.length + ''; html += '
'; } if (publishImages.length < 9) { html += '
+
'; } grid.innerHTML = html; if (publishImages.length > 0) { upload.classList.add('has-images'); } else { upload.classList.remove('has-images'); } document.getElementById('pubImageError').classList.remove('show'); } function removePublishImage(index) { publishImages.splice(index, 1); renderPublishImageGrid(); } var dragImageIndex = null; function handleImageDragStart(e, index) { dragImageIndex = index; e.target.style.opacity = '0.5'; e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', index); } function handleImageDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; } function handleImageDragEnter(e, index) { e.preventDefault(); document.querySelectorAll('.publish-image-item').forEach(function(item) { item.classList.remove('drag-over'); }); if (index !== dragImageIndex) { e.currentTarget.classList.add('drag-over'); } } function handleImageDrop(e, index) { e.preventDefault(); document.querySelectorAll('.publish-image-item').forEach(function(item) { item.classList.remove('drag-over'); }); if (dragImageIndex === null || dragImageIndex === index) return; var item = publishImages.splice(dragImageIndex, 1)[0]; publishImages.splice(index, 0, item); dragImageIndex = null; renderPublishImageGrid(); } function handleImageDragEnd(e) { e.target.style.opacity = '1'; document.querySelectorAll('.publish-image-item').forEach(function(item) { item.classList.remove('drag-over'); }); dragImageIndex = null; } function initAutoComplete() { if (!window.AMap) { amapAutoCompleteReady = false; return; } try { AMap.plugin('AMap.AutoComplete', function() { amapAutocomplete = new AMap.AutoComplete({ city: '', citylimit: false, datatype: 'poi' }); amapAutoCompleteReady = true; }); } catch(e) { amapAutoCompleteReady = false; } } function escHtml(str) { return String(str).replace(/\\/g, '\\\\').replace(/'/g, "\\'"); } function buildLocalDataHTML(query) { var html = ''; var filtered = locationData.filter(function(loc) { return loc.name.toLowerCase().indexOf(query) !== -1 || loc.region.toLowerCase().indexOf(query) !== -1; }); if (filtered.length > 0) { for (var i = 0; i < Math.min(filtered.length, 12); i++) { html += '
'; html += '' + filtered[i].name + ''; html += '' + filtered[i].region + ''; html += '
'; } } return html; } function filterLocationSuggestions() { var input = document.getElementById('publishLocation'); var dropdown = document.getElementById('locationDropdown'); var query = input.value.trim(); if (locationSearchTimer) { clearTimeout(locationSearchTimer); locationSearchTimer = null; } if (!query) { var html = ''; for (var j = 0; j < Math.min(10, locationData.length); j++) { html += '
'; html += '' + locationData[j].name + ''; html += '' + locationData[j].region + ''; html += '
'; } dropdown.innerHTML = html; dropdown.classList.add('show'); return; } dropdown.innerHTML = '
搜索中...
'; dropdown.classList.add('show'); locationSearchTimer = setTimeout(function() { if (amapAutocomplete && amapAutoCompleteReady) { amapAutocomplete.search(query, function(status, result) { var dropdownEl = document.getElementById('locationDropdown'); var resultHtml = ''; if (status === 'complete' && result.tips && result.tips.length > 0) { for (var i = 0; i < Math.min(result.tips.length, 10); i++) { var tip = result.tips[i]; var name = tip.name || ''; var district = tip.district || ''; var address = tip.address || ''; var locStr = tip.location ? tip.location.lng + ',' + tip.location.lat : ''; var region = district || address || ''; if (!name || name === query) continue; var dispRegion = region.replace(name, '').trim(); resultHtml += '
'; resultHtml += '' + name + ''; if (dispRegion) resultHtml += '' + dispRegion + ''; resultHtml += '
'; } } if (!resultHtml) { var queryLower = query.toLowerCase(); resultHtml = buildLocalDataHTML(queryLower); } if (!resultHtml) { resultHtml = '
未找到匹配地点
'; } dropdownEl.innerHTML = resultHtml; dropdownEl.classList.add('show'); }); } else { var dropdownEl = document.getElementById('locationDropdown'); var localHtml = buildLocalDataHTML(query.toLowerCase()); dropdownEl.innerHTML = localHtml || '
未找到匹配地点
'; dropdownEl.classList.add('show'); } }, 350); } function selectLocation(name, region, coords) { document.getElementById('publishLocation').value = name; if (locationSearchTimer) { clearTimeout(locationSearchTimer); locationSearchTimer = null; } document.getElementById('locationDropdown').classList.remove('show'); } function hideLocationDropdown() { setTimeout(function() { if (locationSearchTimer) { clearTimeout(locationSearchTimer); locationSearchTimer = null; } document.getElementById('locationDropdown').classList.remove('show'); }, 200); } function handleTagKeydown(event) { if (event.key === 'Enter') { event.preventDefault(); addTagFromInput(); } } function addTagFromInput() { var input = document.getElementById('publishTagsInput'); var tag = input.value.trim(); if (!tag) return; if (publishTags.indexOf(tag) !== -1) { input.value = ''; return; } if (publishTags.length >= 8) { showToast('最多添加 8 个标签'); input.value = ''; return; } publishTags.push(tag); input.value = ''; renderPublishTags(); } function removePublishTag(index) { publishTags.splice(index, 1); renderPublishTags(); } function renderPublishTags() { var container = document.getElementById('publishTagsContainer'); var html = ''; for (var i = 0; i < publishTags.length; i++) { html += '' + publishTags[i] + ''; } container.innerHTML = html; } function updatePublishWordCount() { var text = document.getElementById('publishContent').value; var count = text.replace(/\s/g, '').length; var el = document.getElementById('publishWordCount'); el.textContent = count + ' / 1000 字'; if (count > 900) { el.className = 'word-count warn'; } else if (count > 1000) { el.className = 'word-count over'; } else { el.className = 'word-count'; } } var currentFeedbackType = 'suggestion'; var currentFeedbackRating = 5; function selectFeedbackType(type, btn) { currentFeedbackType = type; document.querySelectorAll('.feedback-type-btn').forEach(function(b) { b.classList.remove('active'); }); btn.classList.add('active'); var ratingGroup = document.getElementById('fbRatingGroup'); ratingGroup.style.display = type === 'review' ? 'block' : 'none'; } function setFeedbackStar(star) { currentFeedbackRating = star; document.querySelectorAll('#fbStarRating .star').forEach(function(s) { if (parseInt(s.getAttribute('data-star')) <= star) { s.classList.add('active'); } else { s.classList.remove('active'); } }); } function getFeedbacks() { try { var data = localStorage.getItem('travel_feedbacks'); return data ? JSON.parse(data) : []; } catch(e) { return []; } } function saveFeedbacks(data) { localStorage.setItem('travel_feedbacks', JSON.stringify(data)); } function submitFeedback() { if (!currentUser) { document.getElementById('fbLoginHint').style.display = 'block'; return; } document.getElementById('fbLoginHint').style.display = 'none'; var nickname = document.getElementById('feedbackNickname').value.trim(); var contact = document.getElementById('feedbackContact').value.trim(); var content = document.getElementById('feedbackContent').value.trim(); if (!content) { showToast('请填写留言内容'); return; } var newFeedback = { id: Date.now().toString(), name: nickname || '匿名用户', contact: contact, type: currentFeedbackType, content: content, rating: currentFeedbackRating, user: currentUser || null, date: new Date().toLocaleDateString('zh-CN') }; var feedbacks = getFeedbacks(); feedbacks.unshift(newFeedback); saveFeedbacks(feedbacks); document.getElementById('feedbackNickname').value = ''; document.getElementById('feedbackContact').value = ''; document.getElementById('feedbackContent').value = ''; currentFeedbackType = 'suggestion'; currentFeedbackRating = 5; document.querySelectorAll('.feedback-type-btn').forEach(function(b) { b.classList.remove('active'); }); var firstBtn = document.querySelector('.feedback-type-btn[data-type="suggestion"]'); if (firstBtn) firstBtn.classList.add('active'); document.getElementById('fbRatingGroup').style.display = 'none'; document.querySelectorAll('#fbStarRating .star').forEach(function(s) { s.classList.add('active'); }); var successEl = document.getElementById('fbSuccess'); successEl.style.display = 'block'; setTimeout(function() { successEl.style.display = 'none'; }, 3000); renderFeedbackList(); } function renderFeedbackList() { var feedbacks = getFeedbacks(); var listEl = document.getElementById('fbList'); var emptyEl = document.getElementById('fbEmpty'); var countEl = document.getElementById('fbListCount'); countEl.textContent = '(' + feedbacks.length + ' 条)'; if (feedbacks.length === 0) { emptyEl.style.display = 'block'; listEl.innerHTML = ''; } else { emptyEl.style.display = 'none'; } var typeLabels = { suggestion: '功能建议', bug: '问题反馈', review: '使用评价', other: '其他' }; var typeIcons = { suggestion: '💡', bug: '🐛', review: '⭐', other: '💬' }; var html = ''; feedbacks.forEach(function(fb) { var starsHtml = ''; if (fb.type === 'review') { for (var i = 1; i <= 5; i++) { starsHtml += '★'; } } html += '
' + '
' + '' + (fb.type === 'review' ? '' : '') + '' + '
' + '
' + escapeHtml(fb.content) + '
' + '
'; }); listEl.innerHTML = html; updateFeedbackStats(feedbacks); } function deleteFeedback(id) { var feedbacks = getFeedbacks(); var updated = feedbacks.filter(function(f) { return f.id !== id; }); saveFeedbacks(updated); renderFeedbackList(); if (currentUser) { updateMineHeader(); renderMineFeedbacks(); } } function escapeHtml(str) { var div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function updateFeedbackStats(feedbacks) { document.getElementById('fbStatTotal').textContent = feedbacks.length; document.getElementById('fbStatSuggestion').textContent = feedbacks.filter(function(f) { return f.type === 'suggestion'; }).length; document.getElementById('fbStatBug').textContent = feedbacks.filter(function(f) { return f.type === 'bug'; }).length; var avg = '0'; if (feedbacks.length > 0) { var sum = feedbacks.reduce(function(s, f) { return s + f.rating; }, 0); avg = (sum / feedbacks.length).toFixed(1); } document.getElementById('fbStatAvg').textContent = avg; } function showToast(msg) { var toast = document.getElementById('toast'); toast.textContent = msg; toast.classList.add('show'); setTimeout(function() { toast.classList.remove('show'); }, 2500); } var socialPosts = [ { id: 1, userName: '小食妹', avatar: '小', avatarBg: '#e8774a', time: '2小时前', content: '今天在成都建设路发现了一家超棒的烤脑花摊!老板说开了二十年,每天限量100份,去晚了就没了。红油蒜泥一浇,那个香气直接冲上天灵盖!', location: '成都 · 建设路夜市', images: ['linear-gradient(135deg,#e74c3c,#f5a623)', 'linear-gradient(135deg,#f5a623,#e8774a)', 'linear-gradient(135deg,#e8774a,#d35400)'], likes: 128, comments: [ { id: 1001, username: '爱旅行的小美', avatar: '美', avatarBg: '#e74c3c', time: '1小时前', content: '太诱人了!下次去成都一定要去试试', likes: 12, liked: false }, { id: 1002, username: '吃货小王', avatar: '王', avatarBg: '#3498db', time: '30分钟前', content: '建设路真的是美食天堂,每次去都有新发现', likes: 5, liked: false } ], liked: false }, { id: 2, userName: '咖啡控', avatar: '咖', avatarBg: '#8e44ad', time: '5小时前', content: '法租界永福路的老洋房里,藏着一家没有招牌的手冲咖啡馆。Q-Grader认证的咖啡师,每一杯都是艺术品。推荐埃塞俄比亚耶加雪菲,花香和果酸层次分明,尾韵悠长。', location: '上海 · 徐汇区', images: ['linear-gradient(135deg,#8e44ad,#6c3483)'], likes: 89, comments: [ { id: 1003, username: '上海咖啡地图', avatar: '上', avatarBg: '#2ecc71', time: '3小时前', content: '永福路那家我知道!老板是台湾人,手冲确实一流', likes: 8, liked: false } ], liked: false }, { id: 3, userName: '旅行者小王', avatar: '王', avatarBg: '#4caf50', time: '8小时前', content: '环洱海骑行,清晨的湖面如镜,倒映着苍山和天空。在大理,时间是用来浪费的。找一家海景咖啡馆,坐一个下午,看云卷云舒,这就是旅行的意义。', location: '云南 · 大理', images: ['linear-gradient(135deg,#4caf50,#00bcd4)', 'linear-gradient(135deg,#00bcd4,#0097a7)'], likes: 256, comments: [ { id: 1004, username: '大理常驻民', avatar: '大', avatarBg: '#f39c12', time: '5小时前', content: '在大理住了三年,每天看洱海都不会腻', likes: 20, liked: false }, { id: 1005, username: '摄影爱好者', avatar: '摄', avatarBg: '#9b59b6', time: '2小时前', content: '拍了好多照片,洱海的日出真的绝美', likes: 15, liked: false } ], liked: false } ]; var notifications = []; function loadData() { var saved = localStorage.getItem('tandian_data'); if (saved) { var data = JSON.parse(saved); if (data.travelNotes) travelNotes = data.travelNotes; if (data.socialPosts) socialPosts = data.socialPosts; if (data.notifications) notifications = data.notifications; if (data.currentUser) currentUser = data.currentUser; if (data.userAvatar) userAvatar = data.userAvatar; } } function saveData() { localStorage.setItem('tandian_data', JSON.stringify({ travelNotes: travelNotes, socialPosts: socialPosts, notifications: notifications, currentUser: currentUser, userAvatar: userAvatar })); } function renderSocialFeed() { var container = document.getElementById('socialFeed'); if (!container) return; var html = ''; for (var i = 0; i < socialPosts.length; i++) { var post = socialPosts[i]; var displayImages = post.images.slice(0, 3); var imgClass = displayImages.length === 1 ? 'single' : 'multi'; var imagesHtml = '
'; for (var j = 0; j < displayImages.length; j++) { imagesHtml += '
'; } imagesHtml += '
'; html += '
'; html += '
'; html += '
' + post.content + '
'; html += ''; html += '
📍 ' + post.location + '
'; html += imagesHtml; html += ''; html += '
'; html += '
'; for (var k = 0; k < post.comments.length; k++) { var cmt = post.comments[k]; html += '
'; html += '
' + (cmt.avatar || cmt.username.charAt(0)) + '
'; html += '
'; html += '
' + (cmt.username || cmt.user) + '' + cmt.time + '
'; html += '
' + (cmt.content || cmt.text) + '
'; html += ''; html += '
'; } html += '
'; html += ''; html += '
'; html += '
'; } container.innerHTML = html; setTimeout(function() { for (var k = 0; k < socialPosts.length; k++) { var p = socialPosts[k]; var contentEl = document.getElementById('sc-' + p.id); var expandEl = document.getElementById('sx-' + p.id); if (contentEl && expandEl && contentEl.scrollHeight > contentEl.clientHeight) { expandEl.style.display = 'inline-block'; } } }, 0); } function toggleSocialExpand(e, id) { e.stopPropagation(); var contentEl = document.getElementById('sc-' + id); var expandEl = document.getElementById('sx-' + id); if (!contentEl || !expandEl) return; if (contentEl.classList.contains('expanded')) { contentEl.classList.remove('expanded'); expandEl.textContent = '...全文'; } else { contentEl.classList.add('expanded'); expandEl.textContent = '收起'; } } function openCommentModal() { if (!currentUser) { openLoginModal('comment'); return; } quickCommentImages = []; document.getElementById('quickCommentInput').value = ''; document.getElementById('quickCommentLocation').value = ''; renderQuickCommentImageGrid(); document.getElementById('commentModal').style.display = 'flex'; document.body.style.overflow = 'hidden'; } function closeCommentModal() { document.getElementById('commentModal').style.display = 'none'; document.body.style.overflow = ''; quickCommentImages = []; document.getElementById('quickCommentInput').value = ''; document.getElementById('quickCommentLocation').value = ''; renderQuickCommentImageGrid(); } function handleQuickCommentImageSelect(event) { var files = event.target.files; for (var i = 0; i < files.length; i++) { if (!files[i].type.match(/^image\//)) continue; if (quickCommentImages.length >= 9) { showToast('最多上传 9 张图片'); break; } quickCommentImages.push(files[i]); } event.target.value = ''; renderQuickCommentImageGrid(); } function removeQuickCommentImage(index) { quickCommentImages.splice(index, 1); renderQuickCommentImageGrid(); } function renderQuickCommentImageGrid() { var grid = document.getElementById('quickCommentImageGrid'); if (!grid) return; var html = ''; for (var j = 0; j < quickCommentImages.length; j++) { html += '
' + '' + '
'; } grid.innerHTML = html; for (var k = 0; k < quickCommentImages.length; k++) { (function(idx) { var reader = new FileReader(); reader.onload = function(e) { var thumb = document.getElementById('qci-' + idx); if (thumb) thumb.style.backgroundImage = 'url(' + e.target.result + ')'; }; reader.readAsDataURL(quickCommentImages[idx]); })(k); } } function submitQuickComment() { var content = document.getElementById('quickCommentInput').value.trim(); var location = document.getElementById('quickCommentLocation').value.trim(); if (!content) { showToast('请输入评论内容'); return; } var colors = ['#e8774a', '#8e44ad', '#4caf50', '#e74c3c', '#3498db', '#f39c12', '#1abc9c', '#e67e22']; var createPost = function(imageUrls) { var postImages = imageUrls.length > 0 ? imageUrls : [colors[Math.floor(Math.random() * colors.length)]]; socialPosts.unshift({ id: Date.now(), userName: currentUser, avatar: currentUser.charAt(0), avatarBg: colors[Math.floor(Math.random() * colors.length)], time: '刚刚', content: content, location: location, images: postImages, likes: 0, comments: [], liked: false }); document.getElementById('quickCommentInput').value = ''; document.getElementById('quickCommentLocation').value = ''; quickCommentImages = []; renderQuickCommentImageGrid(); closeCommentModal(); renderSocialFeed(); saveData(); showToast('评论已发布!'); }; if (quickCommentImages.length > 0) { var urls = []; var loaded = 0; for (var i = 0; i < quickCommentImages.length; i++) { var reader = new FileReader(); reader.onload = (function(idx) { return function(e) { urls[idx] = e.target.result; loaded++; if (loaded === quickCommentImages.length) createPost(urls); }; })(i); reader.readAsDataURL(quickCommentImages[i]); } } else { createPost([]); } } function openSocialImage(postIndex, displayIndex) { var post = socialPosts[postIndex]; if (!post || !post.images) return; var allImages = post.images; var realImages = allImages.filter(function(img) { return img.indexOf('data:') === 0 || img.indexOf('http') === 0; }); if (realImages.length === 0) return; var actualUrl = allImages[displayIndex]; var lightboxIndex = realImages.indexOf(actualUrl); if (lightboxIndex < 0) lightboxIndex = 0; openLightbox(realImages, lightboxIndex); } function toggleSocialLike(id) { var post = socialPosts.find(function(p) { return p.id === id; }); if (!post) return; post.liked = !post.liked; post.likes += post.liked ? 1 : -1; renderSocialFeed(); saveData(); } function toggleSocialComments(postId) { var el = document.getElementById('scm-' + postId); if (el) el.classList.toggle('open'); } function submitSocialComment(postId) { if (!currentUser) { openLoginModal(); return; } var input = document.getElementById('sci-' + postId); if (!input || !input.value.trim()) return; var post = socialPosts.find(function(p) { return p.id === postId; }); if (!post) return; var colors = ['#e8774a', '#8e44ad', '#4caf50', '#e74c3c', '#3498db', '#f39c12', '#1abc9c', '#e67e22']; var commentObj = { id: Date.now(), username: currentUser, avatar: currentUser.charAt(0), avatarBg: colors[Math.floor(Math.random() * colors.length)], time: '刚刚', content: input.value.trim(), likes: 0, liked: false }; post.comments.push(commentObj); if (post.userName !== currentUser) { notifications.unshift({ id: Date.now(), type: 'comment', postId: postId, user: currentUser, text: input.value.trim(), time: '刚刚', read: false }); } input.value = ''; renderSocialFeed(); saveData(); } function toggleCommentLike(postId, commentIndex) { var post = socialPosts.find(function(p) { return p.id === postId; }); if (!post || !post.comments[commentIndex]) return; var cmt = post.comments[commentIndex]; if (cmt.liked) { cmt.likes--; cmt.liked = false; } else { cmt.likes++; cmt.liked = true; } renderSocialFeed(); } var currentShareNoteId = null; function showShareModal(noteId) { currentShareNoteId = noteId; document.getElementById('shareModal').classList.add('active'); } function hideShareModal() { currentShareNoteId = null; document.getElementById('shareModal').classList.remove('active'); } function copyShareLink() { var note = socialPosts.find(function(p) { return p.id === currentShareNoteId; }); var text = '探店足迹 - 分享精彩探店笔记'; if (note) { text = note.userName + ' 在 ' + note.location + ' 的探店笔记'; } else { var tn = travelNotes.find(function(n) { return n.id === currentShareNoteId; }); if (tn) text = tn.author + ' 在 ' + tn.location + ' 的探店笔记'; } navigator.clipboard.writeText(text).then(function() { var btn = document.getElementById('copyLinkBtn'); if (btn) { btn.innerHTML = '✓ 已复制'; btn.style.background = '#F0FDF4'; btn.style.color = '#10B981'; setTimeout(function() { btn.innerHTML = '🔗 复制链接'; btn.style.background = '#F5F1E8'; btn.style.color = '#2C2C2C'; }, 2000); } }); } loadData(); (function() { var hash = location.hash.replace('#', ''); var validPages = ['home', 'travel', 'tools', 'about', 'feedback', 'mine']; if (hash && validPages.indexOf(hash) !== -1) { navigateTo(hash); } })(); if (currentUser) { document.getElementById('navUsername').textContent = currentUser; document.getElementById('navUsername').style.display = 'inline'; document.getElementById('navAvatar').textContent = currentUser.charAt(0); document.getElementById('navAvatar').style.display = 'flex'; document.getElementById('navLoginBtn').style.display = 'none'; document.getElementById('navLogoutBtn').style.display = 'inline'; updateMineHeader(); } renderHomeStories(); renderStops(); renderNotes(); renderSocialFeed(); // URL 参数检测:新窗口打开详情页 (function() { var params = new URLSearchParams(window.location.search); var noteId = params.get('note'); if (noteId) { var note = travelNotes.find(function(n) { return n.id === parseInt(noteId); }); if (note) { currentDetailNote = note; currentSlide = 0; detailLiked = false; document.querySelectorAll('.page').forEach(function(p) { p.classList.remove('active'); }); document.getElementById('detailOverlay').classList.add('active'); updateNavState('travel'); renderNoteDetail(parseInt(noteId)); } } })(); setTimeout(function() { initHomeMap(); initAutoComplete(); }, 200); loadSavedRoutes(); updateSavedBadge(); loadSubmissions(); renderInspirationRoutes(); // ===== 详情卡片内图片轮播辅助函数 ===== function inspDetailPrev() { if (!window._inspDetailImages || window._inspDetailImages.length <= 1) return; window._inspDetailIdx = (window._inspDetailIdx - 1 + window._inspDetailImages.length) % window._inspDetailImages.length; inspDetailUpdate(); } function inspDetailNext() { if (!window._inspDetailImages || window._inspDetailImages.length <= 1) return; window._inspDetailIdx = (window._inspDetailIdx + 1) % window._inspDetailImages.length; inspDetailUpdate(); } function inspDetailGoTo(idx) { if (!window._inspDetailImages) return; window._inspDetailIdx = idx; inspDetailUpdate(); } function inspDetailUpdate() { var img = document.getElementById('inspDetailImg'); var dots = document.getElementById('inspDetailDots'); if (img && window._inspDetailImages) { img.src = window._inspDetailImages[window._inspDetailIdx]; } if (dots) { var dotEls = dots.querySelectorAll('.detail-img-dot'); for (var d = 0; d < dotEls.length; d++) { dotEls[d].classList.toggle('active', d === window._inspDetailIdx); } } } // ===== 全屏图片 Lightbox ===== var lightboxImages = []; var lightboxIndex = 0; function openLightbox(images, index) { if (!images || images.length === 0) return; lightboxImages = images; lightboxIndex = index || 0; var lb = document.getElementById('imageLightbox'); var prevBtn = document.getElementById('lightboxPrev'); var nextBtn = document.getElementById('lightboxNext'); var counter = document.getElementById('lightboxCounter'); updateLightboxImage(lightboxIndex); if (images.length > 1) { prevBtn.style.display = 'flex'; nextBtn.style.display = 'flex'; counter.style.display = 'block'; } else { prevBtn.style.display = 'none'; nextBtn.style.display = 'none'; counter.style.display = 'none'; } lb.classList.add('active'); document.body.style.overflow = 'hidden'; } function closeLightbox() { document.getElementById('imageLightbox').classList.remove('active'); document.body.style.overflow = ''; lightboxImages = []; lightboxIndex = 0; } function lightboxPrev() { if (lightboxImages.length <= 1) return; lightboxIndex = (lightboxIndex - 1 + lightboxImages.length) % lightboxImages.length; updateLightboxImage(lightboxIndex); } function lightboxNext() { if (lightboxImages.length <= 1) return; lightboxIndex = (lightboxIndex + 1) % lightboxImages.length; updateLightboxImage(lightboxIndex); } function updateLightboxImage(index) { document.getElementById('lightboxImg').src = lightboxImages[index]; var counter = document.getElementById('lightboxCounter'); if (lightboxImages.length > 1) { counter.textContent = (index + 1) + ' / ' + lightboxImages.length; } } document.addEventListener('keydown', function(e) { var lb = document.getElementById('imageLightbox'); if (!lb.classList.contains('active')) return; if (e.key === 'Escape') { closeLightbox(); } else if (e.key === 'ArrowLeft') { lightboxPrev(); } else if (e.key === 'ArrowRight') { lightboxNext(); } });