From f94177c6ede2468f2ca7ca902d47ef302c166da8 Mon Sep 17 00:00:00 2001 From: Lxy Date: Sat, 6 Jun 2026 23:25:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=90=84=E7=A7=8D?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/MapView.vue | 7 +- src/components/QuickPlanPanel.vue | 701 +++++++++++++++--------------- src/components/Workbench.vue | 247 +++++++++-- src/services/aiService.js | 115 ++++- src/stores/itinerary.js | 209 +++++++-- 5 files changed, 856 insertions(+), 423 deletions(-) diff --git a/src/components/MapView.vue b/src/components/MapView.vue index bd9deed..2b6531b 100644 --- a/src/components/MapView.vue +++ b/src/components/MapView.vue @@ -219,7 +219,12 @@ const animateCar = (targetIdx, fromStep = null) => { } watch(() => store.currentStep, () => { updateMarkers(); updateRoute(!!animationFrameId) }) -watch(() => store.points, () => { updateMarkers(); updateRoute() }, { deep: true }) +watch(() => store.points, (newPoints) => { + console.log('[MapView] watch store.points 触发,长度:', newPoints.length) + console.log('[MapView] 站点列表:', newPoints.map(p => p.name)) + updateMarkers() + updateRoute() +}, { deep: true }) watch(() => store.mode, () => { updateRoute() }) watch(() => store.hoveredReplacement, (opt) => { updatePreviewLine(opt) }) watch(() => store.routeSegments, () => { updateRoute() }, { deep: true }) diff --git a/src/components/QuickPlanPanel.vue b/src/components/QuickPlanPanel.vue index 21afd9d..69c70dd 100644 --- a/src/components/QuickPlanPanel.vue +++ b/src/components/QuickPlanPanel.vue @@ -1,150 +1,162 @@ @@ -155,7 +167,6 @@ import { quickPlan } from '../services/aiService' const store = useItineraryStore() -const viewMode = ref('list') const allSchemes = computed(() => store.quickSchemes) const historySchemes = computed(() => store.historySchemeGroups) const historyExpanded = ref(false) @@ -171,16 +182,11 @@ const isGenerating = ref(false) const thinkingText = ref('') const thinkingExpanded = ref(true) -const currentDetailIndex = ref(-1) -const currentDetailSource = ref('current') -const currentDetailHistoryGroup = ref(-1) - -const currentDetailScheme = computed(() => { - if (currentDetailSource.value === 'current' && currentDetailIndex.value >= 0) { - return allSchemes.value[currentDetailIndex.value] || {} - } - if (currentDetailSource.value === 'history' && currentDetailHistoryGroup.value >= 0) { - return historySchemes.value[currentDetailHistoryGroup.value]?.schemes?.[currentDetailIndex.value] || {} +// Preview modal +const previewIndex = ref(-1) +const previewScheme = computed(() => { + if (previewIndex.value >= 0 && previewIndex.value < allSchemes.value.length) { + return allSchemes.value[previewIndex.value] || {} } return {} }) @@ -214,7 +220,19 @@ async function generatePlan() { thinkingText.value = text }) + console.log('[generatePlan] AI 返回结果:', result) + console.log('[generatePlan] schemes 数量:', result.schemes?.length) + const newSchemes = result.schemes || [] + + // 检查每个方案的 points + newSchemes.forEach((scheme, idx) => { + console.log(`[generatePlan] 方案 ${idx} - ${scheme.name}:`, { + pointsCount: scheme.points?.length, + points: scheme.points?.map(p => p.name) + }) + }) + const currentSchemes = [...allSchemes.value] const currentHistory = [...historySchemes.value] @@ -231,41 +249,56 @@ async function generatePlan() { currentHistory.push({ schemes: excess, timestamp: Date.now() }) } + console.log('[generatePlan] 保存方案总数:', currentSchemes.length) store.saveSchemesToStore(currentSchemes, currentHistory) } catch (error) { thinkingText.value = '生成失败:' + (error.message || '请重试') + console.error('[generatePlan] 生成失败:', error) } finally { isGenerating.value = false } } -function selectScheme(index, source, historyGroup) { - currentDetailIndex.value = index - currentDetailSource.value = source - currentDetailHistoryGroup.value = historyGroup ?? -1 - viewMode.value = 'detail' +function regeneratePlan() { + // Clear current schemes and regenerate with same conditions + store.saveSchemesToStore([], [...historySchemes.value]) + generatePlan() } -function useThisScheme() { - const scheme = currentDetailScheme.value - if (!scheme) return +function selectScheme(index) { + const scheme = allSchemes.value[index] + if (!scheme) { + console.error('[selectScheme] 方案不存在,index:', index) + return + } + console.log('[selectScheme] 选择方案:', scheme.name) + console.log('[selectScheme] 方案 points 数量:', scheme.points?.length) + console.log('[selectScheme] 方案 points 列表:', scheme.points?.map(p => p.name)) + + // 先保存方案到 store(深拷贝) store.saveSchemesToStore(allSchemes.value, historySchemes.value) - - const idx = currentDetailSource.value === 'current' ? currentDetailIndex.value : -1 - store.setActiveSchemeIndex(idx) - - store.loadFromAI(scheme) + store.setActiveSchemeIndex(index) + + // 使用深拷贝后的数据加载 + const savedScheme = store.quickSchemes[index] + console.log('[selectScheme] 深拷贝后的方案 points 数量:', savedScheme?.points?.length) + + store.loadFromAI(savedScheme) store.setPhase('workbench') } -function goBackToList() { - viewMode.value = 'list' +function openPreview(index) { + previewIndex.value = index +} + +function closePreview() { + previewIndex.value = -1 } onMounted(() => { if (store.quickSchemes.length > 0) { - viewMode.value = 'list' + // Stay in list view to show existing schemes } }) @@ -456,12 +489,13 @@ onMounted(() => { } .btn-sm { - padding: 6px 16px; - border-radius: 6px; - font-size: 12px; + padding: 8px 18px; + border-radius: 8px; + font-size: 13px; cursor: pointer; border: none; - font-weight: 500; + font-weight: 600; + transition: all 0.2s; } .btn-sm.outline { @@ -475,6 +509,16 @@ onMounted(() => { color: #6c5ce7; } +.btn-sm.primary { + background: linear-gradient(135deg, #6c5ce7, #a29bfe); + color: #fff; +} + +.btn-sm.primary:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(108,92,231,0.3); +} + .plan-card { background: #fff; border-radius: 14px; @@ -483,7 +527,6 @@ onMounted(() => { box-shadow: 0 2px 8px rgba(0,0,0,0.04); border: 2px solid #eee; transition: all 0.2s; - cursor: pointer; } .plan-card:hover { @@ -503,6 +546,7 @@ onMounted(() => { padding: 3px 10px; border-radius: 6px; font-weight: 700; + flex-shrink: 0; } .pc-badge.a { background: #fff3e0; color: #e17055; } @@ -548,248 +592,166 @@ onMounted(() => { } .pc-actions { - text-align: right; + display: flex; + justify-content: flex-end; + gap: 10px; } .pc-btn { - padding: 8px 22px; - background: linear-gradient(135deg, #6c5ce7, #a29bfe); - color: #fff; - border: none; + padding: 8px 20px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; -} - -/* History */ -.history-section { - margin-top: 24px; -} - -.history-section h3 { - font-size: 16px; - color: #2d3436; - margin-bottom: 12px; -} - -.toggle-icon { - font-size: 12px; - margin-left: 8px; - color: #999; -} - -.history-group { - background: #fff; - border-radius: 12px; - padding: 16px; - box-shadow: 0 2px 8px rgba(0,0,0,0.05); - margin-bottom: 12px; -} - -.group-label { - font-size: 12px; - font-weight: 600; - color: #999; - margin-bottom: 10px; - padding-bottom: 6px; - border-bottom: 1px solid #f0f0f0; -} - -.history-card { - background: #f8f9fa; - border: 1px solid #e0e0e0; - border-radius: 8px; - padding: 12px; - margin-bottom: 8px; - cursor: pointer; + border: none; transition: all 0.2s; } -.history-card:last-child { margin-bottom: 0; } - -.history-card:hover { - border-color: #6c5ce7; +.pc-btn.preview { background: #fff; + border: 1.5px solid #6c5ce7; + color: #6c5ce7; } -.history-card h5 { - margin: 8px 0 6px; - font-size: 14px; - color: #333; +.pc-btn.preview:hover { + background: #f0f0f5; } -.card-badge { - display: inline-block; - background: #6c5ce7; +.pc-btn.select { + background: linear-gradient(135deg, #6c5ce7, #a29bfe); color: #fff; - padding: 2px 8px; - border-radius: 4px; - font-size: 11px; - font-weight: 600; } -.card-stats { - display: flex; - gap: 12px; - font-size: 12px; - color: #666; +.pc-btn.select:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(108,92,231,0.3); } -.card-action { - margin-top: 8px; - font-size: 12px; - color: #6c5ce7; - font-weight: 600; +/* Modal Overlay */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + padding: 20px; } -/* Detail View */ -.detail-view { +.modal-content { + background: #fff; + border-radius: 20px; + width: 100%; + max-width: 800px; + max-height: 85vh; display: flex; flex-direction: column; - background: #f5f7fa; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2); + animation: modalIn 0.3s ease; } -.detail-header { - background: linear-gradient(135deg, #6c5ce7, #a29bfe); - color: #fff; - padding: 20px 20px 24px; - position: relative; - border-radius: 16px; - margin-bottom: 16px; -} - -.detail-header h2 { - margin: 12px 0 8px; - font-size: 22px; +@keyframes modalIn { + from { + opacity: 0; + transform: scale(0.95) translateY(-20px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } } -.detail-meta { +.modal-header { display: flex; - gap: 16px; - font-size: 14px; - opacity: 0.9; - margin-bottom: 12px; - flex-wrap: wrap; + justify-content: space-between; + align-items: flex-start; + padding: 24px 28px 16px; + border-bottom: 1px solid #eee; } -.back-btn { - background: rgba(255,255,255,0.2); - border: none; - color: #fff; - padding: 8px 14px; - border-radius: 8px; - font-size: 14px; - cursor: pointer; - transition: background 0.2s; +.modal-header h2 { + font-size: 20px; + font-weight: 700; + color: #2d3436; + margin: 8px 0 0; } -.back-btn:hover { background: rgba(255,255,255,0.3); } - -.use-btn { - padding: 12px 24px; - background: #fff; - color: #6c5ce7; +.modal-close { + width: 32px; + height: 32px; border: none; - border-radius: 10px; - font-size: 15px; - font-weight: 600; + background: #f0f0f5; + border-radius: 50%; + font-size: 20px; + color: #636e72; cursor: pointer; + display: flex; + align-items: center; + justify-content: center; transition: all 0.2s; + flex-shrink: 0; } -.use-btn:hover { - transform: scale(1.02); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); -} - -.scheme-switcher { - background: #fff; - padding: 12px 20px; - border-radius: 12px; - margin-bottom: 16px; - box-shadow: 0 2px 8px rgba(0,0,0,0.04); -} - -.switcher-label { - font-size: 13px; - font-weight: 600; - color: #666; - margin-bottom: 8px; +.modal-close:hover { + background: #6c5ce7; + color: #fff; } -.switcher-btns { +.modal-meta { display: flex; - gap: 8px; - flex-wrap: wrap; -} - -.switch-btn { - padding: 6px 14px; - border: 1.5px solid #dfe6e9; - background: #fff; - border-radius: 6px; - font-size: 13px; - cursor: pointer; - transition: all 0.2s; + gap: 16px; + padding: 12px 28px; + font-size: 14px; color: #636e72; + background: #f8f9fa; + flex-wrap: wrap; } -.switch-btn:hover { - border-color: #6c5ce7; - color: #6c5ce7; -} - -.switch-btn.active { - background: #6c5ce7; - color: #fff; - border-color: #6c5ce7; -} - -.detail-content { - max-width: 800px; - margin: 0 auto; - width: 100%; +.modal-scroll { + overflow-y: auto; + padding: 20px 28px; + flex: 1; } -.detail-highlights, .detail-days h3, .detail-tips h3 { - background: #fff; - border-radius: 12px; - padding: 20px; - margin-bottom: 16px; - box-shadow: 0 2px 8px rgba(0,0,0,0.05); +.modal-section { + margin-bottom: 20px; } -.detail-highlights h3, .detail-days h3, .detail-tips h3 { - margin: 0 0 12px; +.modal-section h3 { font-size: 16px; + font-weight: 700; color: #2d3436; + margin: 0 0 12px; } -.detail-highlights ul { +.modal-section ul { margin: 0; padding-left: 20px; } -.detail-highlights li { +.modal-section li { margin-bottom: 6px; color: #555; line-height: 1.6; } .day-card { - background: #fff; + background: #f8f9fa; border-radius: 12px; padding: 16px; margin-bottom: 12px; - box-shadow: 0 2px 8px rgba(0,0,0,0.05); } .day-header { display: flex; align-items: center; gap: 10px; - margin-bottom: 10px; + margin-bottom: 8px; + flex-wrap: wrap; } .day-badge { @@ -799,6 +761,7 @@ onMounted(() => { border-radius: 4px; font-size: 12px; font-weight: 600; + flex-shrink: 0; } .day-title { @@ -807,6 +770,12 @@ onMounted(() => { color: #333; } +.day-km { + font-size: 12px; + color: #999; + margin-left: auto; +} + .day-desc { margin: 0 0 10px; font-size: 14px; @@ -814,28 +783,71 @@ onMounted(() => { line-height: 1.6; } -.day-spots { +.day-schedule { + margin-bottom: 10px; +} + +.schedule-item { + display: flex; + gap: 10px; + margin-bottom: 8px; +} + +.schedule-time { + font-size: 12px; + font-weight: 600; + color: #6c5ce7; + min-width: 50px; + flex-shrink: 0; +} + +.schedule-content strong { + font-size: 14px; + color: #2d3436; +} + +.schedule-content p { + margin: 4px 0; + font-size: 13px; + color: #555; + line-height: 1.5; +} + +.schedule-desc { + font-size: 12px; + color: #999; + font-style: italic; +} + +.day-waypoints, .day-foods { display: flex; flex-wrap: wrap; gap: 6px; + align-items: center; + margin-top: 8px; } -.spot-tag { - background: #f0f0f5; +.waypoint-label, .food-label { + font-size: 12px; + font-weight: 600; color: #636e72; - padding: 2px 10px; - border-radius: 4px; +} + +.waypoint-tag, .food-tag { + background: #fff; + border: 1px solid #dfe6e9; + padding: 3px 8px; + border-radius: 6px; font-size: 12px; + color: #555; } -.detail-tips { - background: #fff8e1; - border-left: 3px solid #f39c12; - padding: 12px; - border-radius: 0 8px 8px 0; - font-size: 13px; - color: #856404; - line-height: 1.5; +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 16px 28px; + border-top: 1px solid #eee; } @media (max-width: 768px) { @@ -846,5 +858,14 @@ onMounted(() => { .form-row { grid-template-columns: 1fr; } + + .modal-content { + max-height: 95vh; + } + + .modal-header, .modal-meta, .modal-scroll, .modal-footer { + padding-left: 16px; + padding-right: 16px; + } } diff --git a/src/components/Workbench.vue b/src/components/Workbench.vue index 0566528..63f0772 100644 --- a/src/components/Workbench.vue +++ b/src/components/Workbench.vue @@ -24,7 +24,6 @@ {{ getSchemeLabel(i) }} - @@ -60,6 +59,18 @@

{{ point.desc }}

+ + + +
{{ point.km || '—' }}
@@ -74,21 +85,49 @@
第几天
+ + +
+

🗺️ 途径推荐

+
+
+
{{ wp.icon || '📍' }}
+
+
{{ wp.name }}
+
{{ wp.desc }}
+
+
+
+
+

📅 行程安排

- {{ s.time }}{{ s.content }} + {{ s.time }} +
+
{{ s.title || s.content }}
+
{{ s.desc }}
+
-
+ + +

🍽️ 美食推荐

-
- {{ f }} +
+
+
{{ typeof f === 'string' ? '🍜' : (f.icon || '🍜') }}
+
+
{{ typeof f === 'string' ? f : f.name }}
+
{{ f.desc }}
+
+
+

🏨 住宿推荐

-
{{ point.hotel }}
+
{{ point.hotel }}

💡 注意事项

@@ -105,6 +144,9 @@
+ @@ -115,13 +157,14 @@