duqing2026's picture
修复 SWOT 分析页面空白问题 (添加数据迁移逻辑)
258ca69
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Idea Validator Pro - 创意验证专家</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body { font-family: 'Inter', sans-serif; background-color: #f8fafc; }
.slide-enter-active, .slide-leave-active { transition: all 0.3s ease; }
.slide-enter-from, .slide-leave-to { opacity: 0; transform: translateY(10px); }
.canvas-grid {
display: flex;
flex-direction: column;
gap: 1px;
background-color: #e2e8f0;
border: 1px solid #e2e8f0;
}
.canvas-cell { background: white; padding: 1rem; display: flex; flex-direction: column; min-height: 200px; }
.swot-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
}
@media (min-width: 1024px) {
.canvas-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(3, minmax(200px, auto));
}
.cell-problem { grid-area: 1 / 1 / 3 / 2; }
.cell-solution { grid-area: 1 / 2 / 2 / 3; }
.cell-metrics { grid-area: 2 / 2 / 3 / 3; }
.cell-uvp { grid-area: 1 / 3 / 3 / 4; }
.cell-advantage { grid-area: 1 / 4 / 2 / 5; }
.cell-channels { grid-area: 2 / 4 / 3 / 5; }
.cell-segments { grid-area: 1 / 5 / 3 / 6; }
.cell-cost { grid-area: 3 / 1 / 4 / 3; }
.cell-revenue { grid-area: 3 / 3 / 4 / 6; }
}
@media (max-width: 768px) {
.swot-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
{% raw %}
<div id="app" class="min-h-screen flex flex-col">
<!-- Header -->
<header class="bg-gradient-to-r from-blue-600 to-indigo-700 text-white shadow-lg">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
<div class="flex items-center gap-2 cursor-pointer" @click="goHome">
<i class="fa-solid fa-lightbulb text-yellow-300 text-xl"></i>
<h1 class="text-xl font-bold tracking-tight">Idea Validator Pro</h1>
</div>
<div class="flex items-center gap-4">
<div class="relative group" v-if="currentProject">
<button class="text-sm bg-white/20 hover:bg-white/30 px-3 py-1.5 rounded transition flex items-center gap-2">
<i class="fa-solid fa-download"></i> 导出 <i class="fa-solid fa-caret-down text-xs"></i>
</button>
<div class="absolute right-0 top-full mt-2 w-48 bg-white rounded-lg shadow-xl border border-gray-100 hidden group-hover:block z-50 overflow-hidden">
<a @click="exportProject" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 cursor-pointer">
<i class="fa-solid fa-file-code mr-2 text-gray-400"></i> JSON 数据
</a>
<a @click="exportMarkdown" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 cursor-pointer">
<i class="fa-brands fa-markdown mr-2 text-gray-400"></i> Markdown 文档
</a>
</div>
</div>
<a href="https://github.com/duqing026" target="_blank" class="text-white/80 hover:text-white">
<i class="fa-brands fa-github text-xl"></i>
</a>
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-1 bg-gray-50 p-4 sm:p-8 overflow-y-auto">
<!-- Dashboard (Project List) -->
<div v-if="!currentProject" class="max-w-5xl mx-auto">
<div class="flex justify-between items-center mb-8">
<h2 class="text-2xl font-bold text-gray-800">我的创意库</h2>
<button @click="createNewProject" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg shadow transition flex items-center gap-2">
<i class="fa-solid fa-plus"></i> 新建创意
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div v-for="p in projects" :key="p.id"
class="bg-white rounded-xl shadow-sm hover:shadow-md transition cursor-pointer border border-gray-100 overflow-hidden group"
@click="openProject(p.id)">
<div class="h-2 bg-gradient-to-r from-blue-500 to-indigo-500"></div>
<div class="p-6">
<h3 class="text-lg font-bold text-gray-800 mb-2 truncate">{{ p.title || '未命名创意' }}</h3>
<p class="text-gray-500 text-sm mb-4 line-clamp-2 h-10">{{ p.pitch || '暂无简介...' }}</p>
<div class="flex justify-between items-center text-xs text-gray-400">
<span><i class="fa-regular fa-clock mr-1"></i>{{ formatDate(p.updatedAt) }}</span>
<div class="flex gap-2">
<span class="px-2 py-1 bg-gray-100 rounded text-gray-600">{{ p.experiments ? p.experiments.length : 0 }} 实验</span>
</div>
</div>
</div>
<div class="px-6 py-3 bg-gray-50 border-t border-gray-100 flex justify-end opacity-0 group-hover:opacity-100 transition">
<button @click.stop="deleteProject(p.id)" class="text-red-500 hover:text-red-700 text-sm">
<i class="fa-solid fa-trash"></i> 删除
</button>
</div>
</div>
<!-- Empty State -->
<div v-if="projects.length === 0" class="col-span-full text-center py-20 bg-white rounded-xl border border-dashed border-gray-300">
<div class="text-6xl text-gray-200 mb-4"><i class="fa-solid fa-rocket"></i></div>
<p class="text-gray-500 mb-4">还没有任何创意项目</p>
<button @click="createNewProject" class="text-blue-600 hover:underline">立即开始第一个创意验证</button>
</div>
</div>
</div>
<!-- Project Editor -->
<div v-else class="max-w-7xl mx-auto h-full flex flex-col">
<!-- Navigation Tabs -->
<div class="flex flex-wrap gap-2 mb-6 border-b border-gray-200 pb-1">
<button v-for="tab in tabs" :key="tab.id"
@click="currentTab = tab.id"
class="px-4 py-2 rounded-t-lg font-medium text-sm transition-colors relative top-px"
:class="currentTab === tab.id ? 'bg-white text-blue-600 border border-gray-200 border-b-white' : 'text-gray-500 hover:text-gray-700'">
<i :class="tab.icon + ' mr-2'"></i>{{ tab.name }}
</button>
</div>
<!-- Tab Content -->
<div class="bg-white rounded-b-lg rounded-tr-lg shadow-sm border border-gray-200 min-h-[500px] flex-1">
<!-- 1. Concept Tab -->
<div v-if="currentTab === 'concept'" class="p-8 max-w-3xl mx-auto animate-fade-in">
<div class="mb-8">
<label class="block text-sm font-medium text-gray-700 mb-1">项目名称</label>
<input v-model="currentProject.title" @input="save" type="text" class="w-full text-3xl font-bold border-b-2 border-gray-200 focus:border-blue-500 outline-none py-2 bg-transparent placeholder-gray-300" placeholder="给你的创意起个名字">
</div>
<div class="mb-8">
<label class="block text-sm font-medium text-gray-700 mb-2">一句话简介 (Elevator Pitch)</label>
<textarea v-model="currentProject.pitch" @input="save" rows="3" class="w-full p-4 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-100 focus:border-blue-500 outline-none resize-none" placeholder="用一句话描述你的产品解决了谁的什么问题..."></textarea>
<p class="text-xs text-gray-400 mt-2 text-right">💡 提示:我们帮助 [目标用户] 解决 [痛点],通过 [解决方案]。</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">目标用户 (Who)</label>
<textarea v-model="currentProject.who" @input="save" rows="4" class="w-full p-3 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-100 focus:border-blue-500 outline-none" placeholder="谁会最先使用这个产品?"></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">核心痛点 (Why)</label>
<textarea v-model="currentProject.why" @input="save" rows="4" class="w-full p-3 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-100 focus:border-blue-500 outline-none" placeholder="他们现在面临什么困扰?"></textarea>
</div>
</div>
</div>
<!-- 2. Lean Canvas Tab -->
<div v-if="currentTab === 'canvas'" class="p-4 h-full flex flex-col">
<div class="flex justify-between items-center mb-4 px-2">
<h3 class="font-bold text-gray-700">精益画布 (Lean Canvas)</h3>
<button @click="downloadCanvas" class="text-xs bg-gray-100 hover:bg-gray-200 px-3 py-1 rounded text-gray-600">
<i class="fa-solid fa-image mr-1"></i> 下载图片
</button>
</div>
<div id="lean-canvas" class="canvas-grid rounded-lg overflow-hidden shadow-sm flex-1 text-sm">
<canvas-cell title="问题 (Problem)" icon="fa-triangle-exclamation" v-model="currentProject.canvas.problem" area="cell-problem"></canvas-cell>
<canvas-cell title="解决方案 (Solution)" icon="fa-puzzle-piece" v-model="currentProject.canvas.solution" area="cell-solution"></canvas-cell>
<canvas-cell title="独特卖点 (UVP)" icon="fa-gift" v-model="currentProject.canvas.uvp" area="cell-uvp"></canvas-cell>
<canvas-cell title="竞争壁垒 (Advantage)" icon="fa-shield-halved" v-model="currentProject.canvas.advantage" area="cell-advantage"></canvas-cell>
<canvas-cell title="客户细分 (Segments)" icon="fa-users" v-model="currentProject.canvas.segments" area="cell-segments"></canvas-cell>
<canvas-cell title="关键指标 (Metrics)" icon="fa-chart-line" v-model="currentProject.canvas.metrics" area="cell-metrics"></canvas-cell>
<canvas-cell title="渠道 (Channels)" icon="fa-bullhorn" v-model="currentProject.canvas.channels" area="cell-channels"></canvas-cell>
<canvas-cell title="成本结构 (Cost)" icon="fa-file-invoice-dollar" v-model="currentProject.canvas.cost" area="cell-cost"></canvas-cell>
<canvas-cell title="收入来源 (Revenue)" icon="fa-money-bill-wave" v-model="currentProject.canvas.revenue" area="cell-revenue"></canvas-cell>
</div>
</div>
<!-- 3. SWOT Tab (New) -->
<div v-if="currentTab === 'swot'" class="p-6 h-full overflow-y-auto">
<div class="mb-6">
<h3 class="font-bold text-gray-700">SWOT 分析</h3>
<p class="text-sm text-gray-500">评估项目的优势、劣势、机会和威胁。</p>
</div>
<div class="swot-grid">
<!-- Strengths -->
<div class="bg-green-50 rounded-lg p-4 border border-green-100">
<div class="flex items-center gap-2 mb-3 text-green-700 font-bold uppercase tracking-wide">
<i class="fa-solid fa-dumbbell"></i> 优势 (Strengths)
</div>
<canvas-cell title="" icon="" v-model="currentProject.swot.strengths" area=""></canvas-cell>
</div>
<!-- Weaknesses -->
<div class="bg-red-50 rounded-lg p-4 border border-red-100">
<div class="flex items-center gap-2 mb-3 text-red-700 font-bold uppercase tracking-wide">
<i class="fa-solid fa-link-slash"></i> 劣势 (Weaknesses)
</div>
<canvas-cell title="" icon="" v-model="currentProject.swot.weaknesses" area=""></canvas-cell>
</div>
<!-- Opportunities -->
<div class="bg-blue-50 rounded-lg p-4 border border-blue-100">
<div class="flex items-center gap-2 mb-3 text-blue-700 font-bold uppercase tracking-wide">
<i class="fa-solid fa-lightbulb"></i> 机会 (Opportunities)
</div>
<canvas-cell title="" icon="" v-model="currentProject.swot.opportunities" area=""></canvas-cell>
</div>
<!-- Threats -->
<div class="bg-yellow-50 rounded-lg p-4 border border-yellow-100">
<div class="flex items-center gap-2 mb-3 text-yellow-700 font-bold uppercase tracking-wide">
<i class="fa-solid fa-bomb"></i> 威胁 (Threats)
</div>
<canvas-cell title="" icon="" v-model="currentProject.swot.threats" area=""></canvas-cell>
</div>
</div>
</div>
<!-- 4. Experiments Tab -->
<div v-if="currentTab === 'experiments'" class="p-6 bg-gray-50 h-full overflow-y-auto">
<div class="flex justify-between items-center mb-6">
<div>
<h3 class="text-lg font-bold text-gray-800">验证实验</h3>
<p class="text-sm text-gray-500">不要只猜想,去验证!</p>
</div>
<button @click="addExperiment" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded shadow text-sm">
<i class="fa-solid fa-flask mr-1"></i> 新建实验
</button>
</div>
<div class="space-y-4">
<div v-for="(exp, idx) in currentProject.experiments" :key="idx" class="bg-white p-5 rounded-lg border border-gray-200 shadow-sm relative group">
<div class="absolute top-4 right-4 opacity-0 group-hover:opacity-100 transition">
<button @click="removeExperiment(idx)" class="text-gray-400 hover:text-red-500"><i class="fa-solid fa-trash"></i></button>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="col-span-1">
<label class="block text-xs font-bold text-gray-500 uppercase mb-1">假设 (Hypothesis)</label>
<textarea v-model="exp.hypothesis" @change="save" rows="3" class="w-full text-sm p-2 border border-gray-200 rounded bg-yellow-50 focus:border-indigo-500 outline-none" placeholder="如果..."></textarea>
</div>
<div class="col-span-1">
<label class="block text-xs font-bold text-gray-500 uppercase mb-1">实验方法 (Method)</label>
<textarea v-model="exp.method" @change="save" rows="3" class="w-full text-sm p-2 border border-gray-200 rounded focus:border-indigo-500 outline-none" placeholder="我们将..."></textarea>
</div>
<div class="col-span-1">
<label class="block text-xs font-bold text-gray-500 uppercase mb-1">结果/结论 (Result)</label>
<textarea v-model="exp.result" @change="save" rows="3" class="w-full text-sm p-2 border border-gray-200 rounded focus:border-indigo-500 outline-none" :class="exp.status === 'success' ? 'bg-green-50' : (exp.status === 'fail' ? 'bg-red-50' : '')" placeholder="结果是..."></textarea>
</div>
</div>
<div class="mt-3 flex items-center gap-3 pt-3 border-t border-gray-100">
<span class="text-xs font-medium text-gray-500">状态:</span>
<select v-model="exp.status" @change="save" class="text-xs border border-gray-300 rounded px-2 py-1 outline-none">
<option value="planned">📅 计划中</option>
<option value="running">🏃 进行中</option>
<option value="success">✅ 验证成功</option>
<option value="fail">❌ 验证失败</option>
</select>
</div>
</div>
<div v-if="currentProject.experiments.length === 0" class="text-center py-10 text-gray-400 border-2 border-dashed border-gray-200 rounded-lg">
还没有实验。试着添加一个:"如果在朋友圈发海报,会有10个人扫码。"
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- Canvas Cell Component Template -->
<template id="canvas-cell-template">
<div :class="['canvas-cell', area, 'group']">
<div class="flex items-center gap-2 mb-2 text-gray-600">
<i :class="['fa-solid', icon, 'text-gray-400']"></i>
<h4 class="font-bold text-xs uppercase tracking-wider">{{ title }}</h4>
</div>
<div class="flex-1 overflow-y-auto">
<ul class="space-y-1">
<li v-for="(item, idx) in modelValue" :key="idx" class="flex gap-2 group/item">
<span class="text-gray-400 text-xs"></span>
<input v-model="modelValue[idx]" @change="$emit('update:modelValue', modelValue)"
class="w-full text-sm bg-transparent outline-none border-b border-transparent hover:border-gray-200 focus:border-blue-400 pb-0.5"
@keyup.enter="focusNext($event)"
placeholder="...">
<button @click="removeItem(idx)" class="text-gray-300 hover:text-red-400 opacity-0 group-hover/item:opacity-100 px-1 text-xs">×</button>
</li>
</ul>
<button @click="addItem" class="mt-2 text-xs text-blue-500 hover:text-blue-700 opacity-0 group-hover:opacity-100 transition flex items-center gap-1">
<i class="fa-solid fa-plus"></i> 添加
</button>
</div>
</div>
</template>
<script>
const { createApp, ref, computed, onMounted, watch } = Vue;
const CanvasCell = {
template: '#canvas-cell-template',
props: ['title', 'icon', 'area', 'modelValue'],
emits: ['update:modelValue'],
setup(props, { emit }) {
const addItem = () => {
const newList = [...props.modelValue, ''];
emit('update:modelValue', newList);
};
const removeItem = (idx) => {
const newList = [...props.modelValue];
newList.splice(idx, 1);
emit('update:modelValue', newList);
};
return { addItem, removeItem };
}
};
createApp({
components: { CanvasCell },
setup() {
const DEFAULT_PROJECT = {
id: 'demo-1',
title: '智能周报生成器 (示例)',
pitch: '帮助程序员从繁琐的周报中解脱出来,自动从代码提交记录生成高质量周报。',
who: '互联网公司的程序员、技术经理',
why: '每周五写周报很痛苦,容易遗漏工作内容,且浪费开发时间。',
updatedAt: new Date(),
canvas: {
problem: ['写周报浪费时间', '容易遗漏工作内容', '格式不统一,主管难以阅读'],
solution: ['自动读取 Git Commit', 'AI 润色生成自然语言', '支持导出为 Markdown/PDF'],
metrics: ['周报生成时间从30分钟降至2分钟', '用户留存率'],
uvp: ['一键生成', '智能润色', '多平台集成 (Jira/GitHub)'],
advantage: ['先发优势', '高质量的 AI Prompt 库'],
channels: ['技术博客', 'GitHub Trending', '技术社区 (掘金/V2EX)'],
segments: ['中大型互联网公司开发团队', '个人开发者'],
cost: ['OpenAI API 费用', '服务器运维成本'],
revenue: ['个人版免费', '团队版 $5/人/月']
},
swot: {
strengths: ['自动化程度高', '支持多种导出格式'],
weaknesses: ['依赖 OpenAI API,成本较高', '初次配置较繁琐'],
opportunities: ['远程办公趋势增加', '开发者工具市场增长'],
threats: ['GitHub Copilot 等竞品集成类似功能']
},
experiments: [
{
hypothesis: '如果我在 V2EX 发布产品介绍,会有至少 50 人注册试用。',
method: '撰写一篇详细的软文,介绍产品痛点和解决方案,发布到 V2EX 创造节点。',
result: '获得了 120 个注册用户,但只有 10% 留存。',
status: 'success'
}
]
};
const projects = ref([]);
const currentProject = ref(null);
const currentTab = ref('concept');
const tabs = [
{ id: 'concept', name: '概念', icon: 'fa-solid fa-lightbulb' },
{ id: 'canvas', name: '画布', icon: 'fa-solid fa-table-cells' },
{ id: 'swot', name: 'SWOT 分析', icon: 'fa-solid fa-shield-halved' },
{ id: 'experiments', name: '实验', icon: 'fa-solid fa-flask' }
];
const createNewProject = () => {
const newProject = {
id: Date.now().toString(),
title: '未命名创意',
pitch: '',
who: '',
why: '',
updatedAt: new Date(),
canvas: {
problem: [], solution: [], metrics: [], uvp: [],
advantage: [], channels: [], segments: [], cost: [], revenue: []
},
swot: {
strengths: [], weaknesses: [], opportunities: [], threats: []
},
experiments: []
};
projects.value.unshift(newProject);
currentProject.value = newProject;
save();
};
const openProject = (id) => {
currentProject.value = projects.value.find(p => p.id === id);
currentTab.value = 'concept';
};
const goHome = () => {
currentProject.value = null;
};
const deleteProject = (id) => {
if (confirm('确定删除这个项目吗?')) {
projects.value = projects.value.filter(p => p.id !== id);
if (currentProject.value && currentProject.value.id === id) {
currentProject.value = null;
}
save();
}
};
const addExperiment = () => {
currentProject.value.experiments.push({
hypothesis: '', method: '', result: '', status: 'planned'
});
save();
};
const removeExperiment = (idx) => {
currentProject.value.experiments.splice(idx, 1);
save();
};
const formatDate = (dateStr) => {
return new Date(dateStr).toLocaleDateString();
};
const save = () => {
if (currentProject.value) {
currentProject.value.updatedAt = new Date();
}
localStorage.setItem('idea-validator-pro-data', JSON.stringify(projects.value));
};
const load = () => {
const data = localStorage.getItem('idea-validator-pro-data');
if (data) {
try {
projects.value = JSON.parse(data);
} catch(e) {
console.error("Data parse error", e);
projects.value = [];
}
}
// 数据迁移:确保所有项目都有 swot 字段
projects.value.forEach(p => {
if (!p.swot) {
p.swot = { strengths: [], weaknesses: [], opportunities: [], threats: [] };
}
});
if (projects.value.length === 0) {
// 使用 JSON.parse(JSON.stringify(...)) 深拷贝,避免引用问题
projects.value = [JSON.parse(JSON.stringify(DEFAULT_PROJECT))];
save();
}
};
const downloadCanvas = () => {
const element = document.getElementById('lean-canvas');
html2canvas(element).then(canvas => {
const link = document.createElement('a');
link.download = `${currentProject.value.title}-canvas.png`;
link.href = canvas.toDataURL();
link.click();
});
};
const exportProject = () => {
const dataStr = JSON.stringify(currentProject.value, null, 2);
const blob = new Blob([dataStr], {type: "application/json"});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = `project-${currentProject.value.title}.json`;
link.href = url;
link.click();
};
const exportMarkdown = () => {
const p = currentProject.value;
let md = `# ${p.title}\n\n`;
md += `> ${p.pitch}\n\n`;
md += `**目标用户**: ${p.who}\n\n`;
md += `**核心痛点**: ${p.why}\n\n`;
md += `## 精益画布\n\n`;
md += `### 1. 问题 (Problem)\n${p.canvas.problem.map(i => `- ${i}`).join('\n')}\n\n`;
md += `### 2. 解决方案 (Solution)\n${p.canvas.solution.map(i => `- ${i}`).join('\n')}\n\n`;
md += `### 3. 独特卖点 (UVP)\n${p.canvas.uvp.map(i => `- ${i}`).join('\n')}\n\n`;
md += `### 4. 竞争壁垒 (Advantage)\n${p.canvas.advantage.map(i => `- ${i}`).join('\n')}\n\n`;
md += `### 5. 客户细分 (Segments)\n${p.canvas.segments.map(i => `- ${i}`).join('\n')}\n\n`;
md += `### 6. 关键指标 (Metrics)\n${p.canvas.metrics.map(i => `- ${i}`).join('\n')}\n\n`;
md += `### 7. 渠道 (Channels)\n${p.canvas.channels.map(i => `- ${i}`).join('\n')}\n\n`;
md += `### 8. 成本结构 (Cost)\n${p.canvas.cost.map(i => `- ${i}`).join('\n')}\n\n`;
md += `### 9. 收入来源 (Revenue)\n${p.canvas.revenue.map(i => `- ${i}`).join('\n')}\n\n`;
if (p.swot) {
md += `## SWOT 分析\n\n`;
md += `### 优势 (Strengths)\n${p.swot.strengths.map(i => `- ${i}`).join('\n')}\n\n`;
md += `### 劣势 (Weaknesses)\n${p.swot.weaknesses.map(i => `- ${i}`).join('\n')}\n\n`;
md += `### 机会 (Opportunities)\n${p.swot.opportunities.map(i => `- ${i}`).join('\n')}\n\n`;
md += `### 威胁 (Threats)\n${p.swot.threats.map(i => `- ${i}`).join('\n')}\n\n`;
}
md += `## 验证实验\n\n`;
p.experiments.forEach((exp, idx) => {
md += `### 实验 ${idx + 1}\n`;
md += `- **假设**: ${exp.hypothesis}\n`;
md += `- **方法**: ${exp.method}\n`;
md += `- **结果**: ${exp.result}\n`;
md += `- **状态**: ${exp.status}\n\n`;
});
const blob = new Blob([md], {type: "text/markdown"});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = `${p.title}-report.md`;
link.href = url;
link.click();
};
onMounted(() => {
load();
});
return {
projects, currentProject, currentTab, tabs,
createNewProject, openProject, goHome, deleteProject,
addExperiment, removeExperiment,
save, formatDate, downloadCanvas, exportProject, exportMarkdown
};
}
}).mount('#app');
</script>
{% endraw %}
</body>
</html>