This repository has been archived on 2026-08-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Hulumath-Web-Demo/TUTORIAL.md
T
2026-08-08 18:48:29 +08:00

8.6 KiB
Raw Blame History

新项目复用教程

假设你要开一个新仓库,做一个类似的"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,然后改这几处:

# 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 — 拿剧本引擎

数据格式(这是最关键的,任何语言都能读):

{
  "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 提取的核心循环):

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 = '<div>故事结束</div>';
  }
}

function makeChoice(choice) {
  // 应用属性变化
  if (choice.effects) {
    for (const key in choice.effects) {
      currentState.stats[key] = (currentState.stats[key] || 0) + choice.effects[key];
    }
  }
  // 跳到下一节点
  renderNode(choice.next);
}

HTML 骨架

<div id="stats"></div>
<div id="character"></div>
<div id="scene"></div>
<div id="choices"></div>

Step 3 — 拿剧本构建脚本

scripts/build_story.py 复制,它提供了两个辅助函数让你不用手写 JSON:

# N(node_id, 场景文字, 说话人, 选项列表)
# C(选项文字, 下一节点ID, 属性变化...)

N("ch1_start", "你站在路口。", "旁白", [
    C("往左走", "ch1_left", str=5),
    C("往右走", "ch1_right", str=-2),
])

在脚本末尾改输出路径后运行 py scripts/build_story.py 就会生成 JSON 文件。

Step 4 — 拿 MathBTI 测试

  1. 复制 templates/mathbti.htmlstatic/css/mathbti.cssstatic/js/mathbti.jsdata/seed_mathbti.json
  2. 前端逻辑在 mathbti.js 里,读 JSON → 逐题渲染 → 算 4 轴分数 → 映射到 16 种结果(4 位二进制)
  3. 如果你要改题,直接改 data/seed_mathbti.json,或用 scripts/build_mathbti.py 重新生成

Step 5 — 拿 Word 导出脚本

scripts/export_*.pypython-docx 把设定文字导出为 Word。复制后改两处:

  • 脚本内的内容文字(硬编码在 Python dict/string 里)
  • 输出路径(脚本末尾,硬编码的绝对路径)
py -m pip install python-docx
py scripts/export_mathbti_doc.py

四、内容文件怎么搬

你要什么 从哪拿 怎么用
葫芦侠行剧本(8 章 93 节点) data/seed_story.json 直接当 JSON 读,格式见上
数学少年线剧本(85 节点) docs/数学少年线_story.json 同上格式,但属性名不同(shuli/xiayi/xinzhi
MathBTI 全套数据(4 轴 12 题 16 结果) data/seed_mathbti.json 前端 fetch 后渲染问卷
模拟器剧本 data/seed_simulator.json 同剧本格式,属性为 gpa/interest/skill
校友访谈数据 data/seed_alumni.json 线性叙事,four_years.nodes 数组
16 位数学家肖像图 static/img/mathematicians/ 文件名是 4 位二进制(0000~1111.png
小说原稿 docs/novel/第0X章.txt 纯文本,随便用
人物设定文档 docs/novel/数学家小传_*.md Markdown,随便用

五、新项目清单

开新仓库时按这个清单走:

  • 复制 app.py,改掉 API key、数据路径、路由
  • 复制 requirements.txt,删掉不需要的包
  • data/ 目录,放你的 JSON 种子数据
  • templates/ 目录,放你的 HTML 模板
  • static/css/static/js/,放样式和脚本
  • 如果有剧本,照 Step 2 的格式写 JSON,或用 build_story.py 生成
  • 如果有 Word 导出需求,复制 scripts/export_*.py 改内容
  • py app.py 启动,浏览器验证

六、常见坑

  1. 旧 Flask 进程占端口:改完代码后页面不更新?Get-Process python | Stop-Process -Force 杀掉旧进程再重启。
  2. 剧本 JSON 里的中文:文件编码必须是 UTF-8json.load(f) 时加 encoding='utf-8'
  3. build_story.py 输出路径:脚本末尾的 out_path 是硬编码绝对路径,换机器必改。
  4. math_teen 故事文件位置数学少年线_story.jsondocs/ 不在 data/app.py 第 37 行直接指向 docs 目录。
  5. localStorage 存档:前端用 localStorage 保存剧本进度,key 名在 JS 里定义(如 mb_story_save),换项目时注意别冲突。
  6. 静态资源缓存HTML 里引用 CSS/JS 带 ?v=数字 版本号,改了前端代码后递增这个数字,否则浏览器用缓存。