diff --git a/src/components/QuickPlanPanel.vue b/src/components/QuickPlanPanel.vue
index f382eb0..adc4829 100644
--- a/src/components/QuickPlanPanel.vue
+++ b/src/components/QuickPlanPanel.vue
@@ -129,6 +129,7 @@
+
@@ -143,7 +144,10 @@
{{ getSchemeLabel(previewIndex) }}
{{ previewScheme.name }}
-
+
@@ -223,6 +227,7 @@
import { ref, computed, onMounted } from 'vue'
import { useItineraryStore } from '../stores/itinerary'
import { quickPlan } from '../services/aiService'
+import { exportToMarkdown } from '../services/exportService'
const store = useItineraryStore()
@@ -393,6 +398,12 @@ function regeneratePlan() {
generatePlan()
}
+function exportSingleScheme(index) {
+ const scheme = allSchemes.value[index]
+ if (!scheme) return
+ exportToMarkdown(scheme, `${scheme.name || '行程规划'}.md`)
+}
+
function selectScheme(index) {
const scheme = allSchemes.value[index]
if (!scheme) {
@@ -424,6 +435,13 @@ function closePreview() {
previewIndex.value = -1
}
+function exportPreviewScheme() {
+ if (previewIndex.value < 0) return
+ const scheme = previewScheme.value
+ if (!scheme) return
+ exportToMarkdown(scheme, `${scheme.name || '行程规划'}.md`)
+}
+
onMounted(() => {
if (store.quickSchemes.length > 0) {
// Stay in list view to show existing schemes
@@ -832,6 +850,16 @@ onMounted(() => {
background: #f0f0f5;
}
+.pc-btn.export {
+ background: #fff;
+ border: 1.5px solid #00b894;
+ color: #00b894;
+}
+
+.pc-btn.export:hover {
+ background: #f0f0f5;
+}
+
.pc-btn.select {
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
color: #fff;
@@ -888,6 +916,12 @@ onMounted(() => {
border-bottom: 1px solid #eee;
}
+.modal-header-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
.modal-header h2 {
font-size: 20px;
font-weight: 700;
diff --git a/src/components/Workbench.vue b/src/components/Workbench.vue
index 63f0772..2c697f9 100644
--- a/src/components/Workbench.vue
+++ b/src/components/Workbench.vue
@@ -24,6 +24,9 @@
{{ getSchemeLabel(i) }}
+
@@ -160,6 +163,7 @@
import { ref, computed, watch } from 'vue'
import { useItineraryStore } from '../stores/itinerary'
import MapView from './MapView.vue'
+import { exportToMarkdown } from '../services/exportService'
const store = useItineraryStore()
const mapView = ref(null)
@@ -229,6 +233,35 @@ const toggleRoute = () => {
}
}
+function exportCurrentScheme() {
+ const idx = store.activeSchemeIndex
+ if (idx < 0 || idx >= store.quickSchemes.length) {
+ // 导出当前 points 数据
+ const scheme = {
+ name: '我的行程',
+ route: store.points.map(p => p.name).join(' → '),
+ days: store.points.length,
+ totalKm: store.totalKm,
+ totalDriveTime: store.totalDriveTime,
+ budget: '—',
+ highlights: [],
+ daysDetail: store.points.map((p, i) => ({
+ location: p.name,
+ desc: p.desc,
+ km: p.km,
+ driveTime: p.driveTime,
+ schedule: p.schedule || [],
+ foods: p.foods || [],
+ waypoints: p.waypoints || []
+ })),
+ tips: ''
+ }
+ exportToMarkdown(scheme, '我的行程.md')
+ } else {
+ exportToMarkdown(store.quickSchemes[idx], `${store.quickSchemes[idx].name || '行程规划'}.md`)
+ }
+}
+
// 监听 points 和 phase 变化,当进入工作台且有有效数据时自动获取真实路径
watch([() => store.points, () => store.phase], ([newPoints, newPhase]) => {
console.log('[Workbench] watch 触发 - phase:', newPhase, 'points 长度:', newPoints.length)
@@ -319,6 +352,28 @@ watch([() => store.points, () => store.phase], ([newPoints, newPhase]) => {
color: #6c5ce7;
}
+.wb-export-btn {
+ padding: 6px 14px;
+ border-radius: 6px;
+ font-size: 12px;
+ cursor: pointer;
+ border: 1.5px solid #6c5ce7;
+ background: #fff;
+ color: #6c5ce7;
+ transition: all 0.2s;
+ font-weight: 500;
+}
+
+.wb-export-btn:hover:not(:disabled) {
+ background: #6c5ce7;
+ color: #fff;
+}
+
+.wb-export-btn:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
/* Wrap layout */
.wb-wrap {
display: grid;
diff --git a/src/services/exportService.js b/src/services/exportService.js
new file mode 100644
index 0000000..783dc14
--- /dev/null
+++ b/src/services/exportService.js
@@ -0,0 +1,99 @@
+// 行程导出功能
+export function exportToMarkdown(scheme, filename = null) {
+ if (!scheme) return
+
+ const md = generateMarkdown(scheme)
+ const blob = new Blob([md], { type: 'text/markdown;charset=utf-8' })
+ const url = URL.createObjectURL(blob)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = filename || `${scheme.name || '行程规划'}.md`
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ URL.revokeObjectURL(url)
+}
+
+function generateMarkdown(scheme) {
+ let md = `# ${scheme.name || '行程规划'}\n\n`
+
+ // 基本信息
+ md += `## 基本信息\n\n`
+ md += `- **路线**:${scheme.route || '—'}\n`
+ md += `- **天数**:${scheme.days || '—'}天\n`
+ md += `- **总里程**:~${scheme.totalKm || '—'}km\n`
+ md += `- **驾驶时间**:~${scheme.totalDriveTime || '—'}小时\n`
+ md += `- **预算**:${scheme.budget || '—'}\n\n`
+
+ // 亮点
+ if (scheme.highlights && scheme.highlights.length > 0) {
+ md += `## 行程亮点\n\n`
+ scheme.highlights.forEach(h => {
+ md += `- ${h}\n`
+ })
+ md += '\n'
+ }
+
+ // 每日行程
+ if (scheme.daysDetail && scheme.daysDetail.length > 0) {
+ md += `## 每日行程\n\n`
+ scheme.daysDetail.forEach((day, idx) => {
+ md += `### Day ${idx + 1}:${day.location || '未知地点'}\n\n`
+ if (day.desc) md += `${day.desc}\n\n`
+ if (day.km) md += `- **里程**:${day.km}km\n`
+ if (day.driveTime) md += `- **驾驶时间**:${day.driveTime}h\n\n`
+
+ if (day.spots && day.spots.length > 0) {
+ md += `**途经景点**:${day.spots.join('、')}\n\n`
+ }
+
+ // 详细日程
+ if (day.schedule && day.schedule.length > 0) {
+ md += `#### 日程安排\n\n`
+ md += `| 时间 | 行程 | 说明 |\n`
+ md += `|------|------|------|\n`
+ day.schedule.forEach(s => {
+ md += `| ${s.time || '—'} | ${s.title || s.content || '—'} | ${s.desc || '—'} |\n`
+ })
+ md += '\n'
+ }
+
+ // 途径推荐
+ if (day.waypoints && day.waypoints.length > 0) {
+ md += `#### 途径推荐\n\n`
+ day.waypoints.forEach(wp => {
+ md += `- **${wp.icon || '📍'} ${wp.name}**:${wp.desc || '—'}\n`
+ })
+ md += '\n'
+ }
+
+ // 美食推荐
+ if (day.foods && day.foods.length > 0) {
+ md += `#### 美食推荐\n\n`
+ day.foods.forEach(f => {
+ const name = typeof f === 'string' ? f : (f.name || f)
+ const icon = typeof f === 'object' ? (f.icon || '🍜') : '🍜'
+ const desc = typeof f === 'object' ? (f.desc || '') : ''
+ md += `- ${icon} **${name}**${desc ? `:${desc}` : ''}\n`
+ })
+ md += '\n'
+ }
+
+ // 住宿
+ if (day.hotel) {
+ md += `**住宿推荐**:${day.hotel}\n\n`
+ }
+
+ md += '---\n\n'
+ })
+ }
+
+ // 旅行贴士
+ if (scheme.tips) {
+ md += `## 旅行贴士\n\n${scheme.tips}\n\n`
+ }
+
+ md += `> 由智能行程规划系统生成\n`
+
+ return md
+}