From 0e4fa065a33e30d0438150d1bbc85bc17482b52f Mon Sep 17 00:00:00 2001 From: huluxia <2056300012@qq.com> Date: Sat, 8 Aug 2026 18:48:29 +0800 Subject: [PATCH] update tutorial --- TUTORIAL.md | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 TUTORIAL.md diff --git a/TUTORIAL.md b/TUTORIAL.md new file mode 100644 index 0000000..d44ca2e --- /dev/null +++ b/TUTORIAL.md @@ -0,0 +1,256 @@ +# 新项目复用教程 + +> 假设你要开一个新仓库,做一个类似的"Flask + 互动剧本 + 人格测试"项目。本教程告诉你从这个老仓库拿什么、怎么拿、怎么改。 + +--- + +## 一、先搞清楚这个项目有哪几块可复用 + +| 模块 | 核心文件 | 能干嘛 | +|------|---------|--------| +| **Flask 后端骨架** | `app.py` | 路由、API、SQLite、gzip 压缩、静态缓存 | +| **互动剧本引擎** | `data/seed_story.json` + `desktop.js` 中的 story 相关函数 | 读取 JSON 剧本 → 前端渲染场景文字 + 选项按钮 + 属性变化 | +| **MathBTI 人格测试** | `data/seed_mathbti.json` + `templates/mathbti.html` + `mathbti.css/js` | 12 题测出 16 种结果,前端自包含可独立部署 | +| **卡片/抽卡系统** | `desktop.js` 中的 card/gacha 相关函数 | 16 张人物卡,读知识卡片攒抽卡次数,保底机制 | +| **内容构建脚本** | `scripts/build_*.py` | 用 Python dict 生成种子 JSON | +| **Word 导出脚本** | `scripts/export_*.py` | 把人物设定导出为 docx | +| **小说/设定原稿** | `docs/novel/*.txt` | 纯文本,可直接喂给任何文本处理流程 | + +--- + +## 二、新项目规划建议 + +### 场景 A:做一个新互动剧本项目(最常见) + +你只需要:**Flask 骨架 + 剧本引擎 + 剧本数据**。 + +``` +new-project/ +├── app.py # 从老项目复制,改掉路径和 API key +├── data/ +│ └── my_story.json # 你的新剧本(照着老格式写) +├── templates/ +│ └── index.html # 从 desktop.html 简化,或自己写 +├── static/ +│ ├── css/ +│ └── js/ +└── requirements.txt +``` + +### 场景 B:做一个人格/心理测试项目 + +你只需要:**MathBTI 前端 + 数据格式**。 + +``` +new-project/ +├── data/ +│ └── seed_mytest.json # 你的测试题+结果(照 mathbti 格式写) +├── templates/ +│ └── test.html # 从 mathbti.html 改 +├── static/ +│ ├── css/ +│ └── js/ +└── scripts/ + └── build_mytest.py # 从 build_mathbti.py 改 +``` + +### 场景 C:纯内容搬运(不要代码) + +你只需要 `docs/` 和 `data/*.json` 里的文本和数据,用自己的技术栈重新实现前端。 + +--- + +## 三、逐步操作 + +### Step 1 — 拿 Flask 骨架 + +从老项目复制 `app.py`,然后改这几处: + +```python +# 1. 改 DeepSeek API Key(第 83 行附近) +DEEPSEEK_API_KEY = "你的key" + +# 2. 改数据路径(第 30~40 行) +# 删掉你不需要的路径变量,加上你自己的 +DATA_DIR = os.path.join(BASE_DIR, "data").replace("\\", "/") +MY_STORY_PATH = os.path.join(DATA_DIR, "my_story.json").replace("\\", "/") + +# 3. 改路由(第 258 行起) +# 删掉不需要的 @app.route,加上你自己的 +@app.route("/") +def index(): + return render_template("index.html") + +# 4. 改 API 函数 +# 照着 api_story() 的写法,load 你的 JSON 返回给前端 +``` + +### Step 2 — 拿剧本引擎 + +**数据格式**(这是最关键的,任何语言都能读): + +```json +{ + "title": "我的剧本", + "start_node": "ch1_start", + "nodes": { + "ch1_start": { + "scene": "场景描述文字。用 \\n 换行。", + "character": "旁白", + "choices": [ + { + "text": "选项A的文字", + "next": "ch1_choice_a", + "effects": { "str": 5 } + }, + { + "text": "选项B的文字", + "next": "ch1_choice_b", + "effects": { "str": -3 } + } + ] + }, + "ch1_choice_a": { + "scene": "你选了A之后的故事...", + "character": "NPC", + "choices": [...] + } + } +} +``` + +**前端渲染逻辑**(从 `desktop.js` 提取的核心循环): + +```javascript +let storyData = null; +let currentState = { currentNode: null, stats: {} }; + +async function loadStory() { + const r = await fetch('/api/story'); + storyData = (await r.json()).data; + const start = storyData.start_node; + const node = storyData.nodes[start]; + renderNode(start); +} + +function renderNode(nodeId) { + const node = storyData.nodes[nodeId]; + if (!node) return; + currentState.currentNode = nodeId; + localStorage.setItem('my_story_save', JSON.stringify(currentState)); + + // 渲染场景文字 + document.getElementById('scene').textContent = node.scene || ''; + // 渲染说话人 + document.getElementById('character').textContent = node.character || ''; + + // 渲染选项按钮 + const choicesEl = document.getElementById('choices'); + choicesEl.innerHTML = ''; + for (const choice of (node.choices || [])) { + const btn = document.createElement('button'); + btn.textContent = choice.text; + btn.onclick = () => makeChoice(choice); + choicesEl.appendChild(btn); + } + + // 没有选项 = 故事结束 + if (!node.choices || node.choices.length === 0) { + choicesEl.innerHTML = '