No description
  • TypeScript 90.8%
  • JavaScript 4.7%
  • CSS 4.2%
  • HTML 0.3%
Find a file
2026-10-24 18:57:20 +08:00
scripts fix: files 2026-09-27 22:06:11 +08:00
server fix: production 2026-10-24 18:57:20 +08:00
src fix: production 2026-10-24 18:57:20 +08:00
index.html feat: worable 2026-09-27 21:09:19 +08:00
LICENSE feat: Licence 2026-09-27 22:11:48 +08:00
package.json feat: worable 2026-09-27 21:09:19 +08:00
README.md fix: production 2026-10-24 18:57:20 +08:00
tsconfig.json feat: workable demo 2026-09-26 23:25:06 +08:00
vite.config.ts feat: worable 2026-09-27 21:09:19 +08:00

NURSING://DECODE — 護理臨床英文情境任務

2026 全國智慧健康照護創新創業競賽 Demo(國立臺南護理專科學校主辦) 主題:遊戲化學習 — 護理臨床英文判讀、醫療縮寫、ISBAR 結構化交班


架構

/                     ← 前端(Vite + TypeScript + GSAP)
/server/              ← 後端(Node + Express + node:sqlite + JWT)
                        └─ src/question.json   ← 整個遊戲的單一題庫來源
  • 前端:純 TS(無框架)+ GSAP。啟動時強制 fetch /api/questions/all 取題庫,後端沒起就顯示錯誤頁
  • 後端:JWT auth + bcrypt hash + Express admin API + 題庫 /api/questions/all;可選擇有同時 serve 前端 dist/
  • 題庫:server/src/question.json 單一檔案管所有內容(vocab / patient / isbar / rubric / mission card / mission questions),schema 見 server/src/question.schema.md
  • 部署:單機一體(Express serve API + dist)或前後分離,見 §正式上線

正式上線(Production Deployment)

上線前請確認 Node.js ≥ 22.5(後端依賴內建 node:sqlite)。

準備:決定部署拓撲

方案 說明 適用
A. 單機一體(推薦) Express 同時 serve API + 前端 dist/,一個 port 搞定 VPS、競賽評審機、demo 教室
B. 前後分離 前端丟 CDN/靜態主機,後端只跑 API 高流量、前端要快取

以下以 方案 A 為主,B 要看 §前後分離部署。


方案 A:單機一體(十條指令)

1. Build 前端

# 在 / 目錄
npm install
VITE_API_BASE_URL="" npm run build     # 空字串 = same-origin(前端 fetch /api/*)
# 產生 dist/ 目錄

⚠️ 預設 npm run build 沒帶 VITE_API_BASE_URL 會用 same-origin("")。 若是要部署前後分離,設成 API 主機網址:VITE_API_BASE_URL="https://api.example.com" npm run build

2. Build 後端

cd server
npm install
npm run build                          # tsc → dist/

3. 設定環境變數

cd server
cp env.example .env         # 檔名是 env.example(不帶點)
vim .env

生產必改 3 個:

變數 為什麼要設
JWT_SECRET 沒設會用 dev secret,JWT 可被偽造。隨機 50+ 字元
DEFAULT_ADMIN_PASSWORD 第一次啟動建立 admin 的初始密碼。首映後立刻登入改掉
CORS_ORIGIN 單機一體留空即可(same-origin 不進 CORS)

隨機 secret 快速產生:

node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"

4. 檢查題庫存在

ls server/src/question.json            # 必須存在
node /workspace/scripts/validate-questions.mjs   # 跑一次確認結構沒壞

5. 啟動(測試)

cd server
node dist/index.js

應看到:

📦 Serving frontend from /.../dist
🩺 NURSING://DECODE server listening on http://localhost:8787

6. 開瀏覽器驗證

http://<主機>:8787 → 應看到首頁 + 可登入。

7. 設成 system service(systemd)

sudo tee /etc/systemd/system/nursing-decode.service <<'EOF'
[Unit]
Description=NURSING://DECODE nursing handoff training
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/nursing-decode/server
EnvironmentFile=/opt/nursing-decode/server/.env
ExecStart=/usr/bin/node dist/index.js
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now nursing-decode
sudo journalctl -u nursing-decode -f        # 查看 log

8. HTTPS(Caddy 最簡)

# /etc/caddy/Caddyfile
demo.example.com {
  reverse_proxy localhost:8787
}
sudo systemctl reload caddy

自動取得 Let's Encrypt 憑證。生產一定要 HTTPS(JWT token 明文傳輸不保護)。

9. 上線前檢查清單

  • JWT_SECRET 改成隨機值
  • DEFAULT_ADMIN_PASSWORD 改成強密碼(or 首登後到「學生帳號管理」把 admin 密碼改掉)
  • HTTPS(Caddy/nginx/certbot 任一)
  • server/data/users.db 會定期備份(cp users.db backup/users-$(date +%F).db 進 cron)
  • Admin console /#/admin/login 只有老師知道
  • Server 有跑 health check:curl -sf http://localhost:8787/api/health

10. 環境變數總表(全部 server 用得到)

變數 預設 說明
NODE_ENV — 設 production 會在漏設 JWT_SECRET 時印警告
PORT 8787 Express 監聽 port
JWT_SECRET dev secret ⚠️ 生產必設隨機
JWT_EXPIRES_IN 8h token 過期時間
DB_PATH ./data/users.db SQLite 檔位置
QUESTION_FILE 自動嘗試主目錄 見下
CORS_ORIGIN localhost:5173,... 生產單機留空
FRONTEND_DIR ../dist(相對 server/) 前端 build 目錄
DEFAULT_ADMIN_USERNAME admin
DEFAULT_ADMIN_PASSWORD admin1234 ⚠️ 生產必改

QUESTION_FILE 搜尋順序(未設環境變數時):

  1. 本檔同目錄(dev: server/src/question.json)
  2. dist/../src/question.json(prod 落在 server/dist/ 時向上找 server/src/)
  3. 兩個都找不到 → server 拒絕啟動並印錯誤

部署時需要齊全的檔案

所有路徑以 repo 根目錄 / 為基準。這些檔案必須被包含在部署內容中:

/
├─ index.html
├─ package.json / package-lock.json / tsconfig.json / vite.config.ts
├─ README.md / LICENSE
├─ src/                     # 完整目錄
├─ scripts/                 # 完整目錄
├─ server/
│  ├─ package.json / package-lock.json / tsconfig.json
│  ├─ env.example
│  └─ src/                  # 完整目錄(含 question.json / question.schema.md / types/)

上完線才需建立的檔案(server 啟動後自動產生,不屬於部署):

server/.env                 # 從 env.example 複製後修改(機密)
server/data/users.db        # server 啟動自動建立
/dist/                      # 前端 build 產生
/server/dist/               # 後端 build 產生

前後分離部署

如果要把前端丟 Netlify / Vercel / Cloudflare Pages 等靜態加速:

# 1. build 前端時帶 API 主機網址
VITE_API_BASE_URL="https://api.example.com" npm run build

# 2. dist/ 整包傳到靜態服務

# 3. server/.env 補 CORS 讓 API 接受那個 origin
CORS_ORIGIN="https://frontend.example.com"

注意:

  • VITE_API_BASE_URL 是 build-time 嵌入,日後要改 API 位置要重 build 前端
  • 前端抓不到 API 時會顯示「後端未連線」錯誤頁(含目標位址)

從頭到尾的完整指令(最快 5 分鐘)

# 主機上
git clone <repo> /opt/nursing-decode && cd /opt/nursing-decode

# 前端
npm install
VITE_API_BASE_URL="" npm run build

# 後端
cd server
npm install
npm run build

# 產隨機 secret 寫 .env(server/env.example 有完整欄位可參考)
JWT_SECRET=$(node -e "console.log(require('crypto').randomBytes(48).toString('hex'))")
cat > .env <<EOF
NODE_ENV=production
PORT=8787
JWT_SECRET=${JWT_SECRET}
DEFAULT_ADMIN_PASSWORD=$(openssl rand -hex 12)
EOF

# 第一次啟動(先在手動 shell 跑,看預設 admin 密碼是否正確印出)
node dist/index.js &
sleep 1
curl -s http://localhost:8787/api/health    # 應回 {"ok":true,...}

# 掛 systemd + Caddy 依上面步驟

快速啟動(本機 demo)

1. 啟動後端

cd server
npm install
npm run dev        # 會在 http://localhost:8787 啟動

第一次啟動會自動建立預設管理員:

  • 帳號 admin,密碼 admin1234(請立即修改)

2. 啟動前端

# 在 / 目錄
npm install
npm run dev        # http://localhost:5173

3. 老師批量建學生帳號

  1. 開 http://localhost:5173/#/admin/login
  2. 用 admin / admin1234 登入
  3. 「學生帳號管理」→ 把 CSV 貼上批量建立:
student001,P@ss1234,王小明,S110001
student002,P@ss1234,李小華,S110002

4. 學生玩 + 上傳成績

  1. 學生開 http://localhost:5173/#/login
  2. 用自己的帳號登入
  3. 玩完五關 → 結算頁按「上傳成績」
  4. 老師回 admin「成績查詢」即可看到 + 匯出 CSV

遊戲流程

# 關卡 內容
1 EXPLORE 探索 瀏覽病人資料 + 收錄詞彙 + 答 3 題快速確認
2 DECODE 解碼 20 詞 4 選 1 測驗(詞彙熟練度記錄)
3 UNDERSTAND 理解 情境填空(詞彙熟練度記錄)
4 ORGANIZE 整理 ISBAR 15 張卡拖曳 → 評核項目 1–15(內容面 77 分)
5 MISSION 任務 溝通面 5 題(評核項目 16–20, 23 分)

結算頁顯示 ISBAR 20 項評核總分(100 分)+ 五關雷達圖。

及格標準:≥ 80 分(依競賽企劃書)


ISBAR 評核標準(20 項 = 99 分)

實際配分以 server/src/question.json 的 rubric[] 為準。企劃書原規劃 100,目前 COMM 五項合計 22(非 23),總分 99;及格門檻 ≥ 80 不變(寫死於 src/stages/rubric-const.ts)。

區 項目 配分
I 介紹 1.自我介紹 2.主訴 3+3 = 6
S 情境 3.診斷現況 4.生命徵象 5.本班重要事件 5+5+5 = 15
B 背景 6.入院經過 7.病史 8.藥物過敏史 4+4+5 = 13
A 評估 9.檢查數據 10.目前治療 11.臨床判斷 12.身體評估 6+5+8+8 = 27
R 建議 13.監測計畫 14.後續檢查 15.建議請求 5+6+5 = 16
小計(內容面) 1–15 77
溝通面 16.邏輯順序 17.精簡度 18.完整度 19.用詞 20.語速 5+5+4+4+4 = 22
總計 20 項 99

學習追蹤機制

機制 說明
詞彙熟練度 每個 vocab 答題會記錄,0–100 分(時間衰減 30 天),4 色顯示
評核項目熟練度 每個 rubric 項目獨立記錄,複習頁可視覺化弱點
書籤 vocab / ISBAR 卡 / patient 段落都可 🔖,複習頁三 tab 瀏覽
複習模式 /review 頁面:ISBAR 五區雷達圖 + 詞彙網格 + TOP5 弱點 + 書籤
AARRR 追蹤 自動記錄完成關卡(activation / retention)

隱私與資安設計

項目 做法
病人資料 全虛構(陳美麗 68F 肺炎)
個人資料 僅存學號、姓名(無身分證/聯絡方式)
密碼 bcrypt(10) hash
傳輸 JWT 8h 過期
病歷分離 後端無存放任何病歷 ↔ 學號連結
用途 教育用途,僅記錄學習表現

技術規格

項 內容
前端 TypeScript 5 (strict) + Vite + GSAP 3
資料(前端) 啟動時 fetch /api/questions/all → 全程在記憶體;localStorage 只留 bookmarks / proficiency
題庫 server/src/question.json(單一 source of truth)
後端 Express + node:sqlite(Node ≥ 22.5 內建,零 native module) + JWT + bcryptjs
動畫 GSAP 3(拖曳/轉場/數字滾動)
特效 Canvas 粒子 + ECG 動畫 + CSS 玻璃擬態
音效 Web Audio API 即時合成(零外部音檔)

檔案結構

/
├─ index.html
├─ src/
│  ├─ main.ts               # 進入點(fetch /api/questions/all → bootstrap)
│  ├─ config.ts             # 集中設定(apiBaseUrl 等)
│  ├─ core/
│  │  ├─ types.ts           # 前端合約(re-export server/src/types/question.ts)
│  │  ├─ db.ts              # 輕量 localStorage store(只裝 bookmarks / proficiency)
│  │  ├─ game.ts            # 計分 + rubric + duration tracking
│  │  ├─ router.ts          # hash router + auth guard
│  │  ├─ auth.ts            # JWT 管理
│  │  ├─ api.ts             # fetch 封裝
│  │  ├─ proficiency.ts     # 熟練度引擎
│  │  └─ bookmarks.ts       # 書籤 CRUD
│  ├─ fx/                   # 粒子/ECG/轉場/回饋
│  ├─ stages/
│  │  ├─ login.ts           # 學生登入
│  │  ├─ home.ts            # 主視覺
│  │  ├─ explore.ts         # + 🔖 + 熟練度 dot + 短答題
│  │  ├─ decode.ts          # + 🔖 + 熟練度 dot(mc / drag-match / clue-solve)
│  │  ├─ understand.ts      # + 🔖 + fill-blank
│  │  ├─ organize.ts        # ISBAR 15 卡拖曳 + rubric 計分 + 🔖
│  │  ├─ mission.ts         # 溝通面 5 題(multi/single+compare/ordered)+ 計時/提示
│  │  ├─ results.ts         # rubric 報告 + 上傳成績
│  │  ├─ review.ts          # 複習中心(雷達圖+書籤三 tab)
│  │  ├─ rubric-const.ts    # RUBRIC_PASS_SCORE = 80
│  │  └─ admin/
│  │      ├─ login.ts
│  │      ├─ dashboard.ts
│  │      ├─ users.ts       # 批量建號 + 列表
│  │      └─ scores.ts      # 成績篩選 + 匯出 CSV
│  ├─ components/
│  │  └─ feedback-modal.ts  # 答題共用 modal(對/錯/漏選/干擾)
│  └─ styles/
├─ scripts/
│  ├─ validate-questions.mjs        # 題庫 validator(結構 + crossRef + kebab id)
│  └─ build/build-question-json.mjs # 一次性 migration(已跑過,留著參考)
└─ server/
   ├─ src/
   │  ├─ index.ts                   # Express 進入點(含 /api/questions/all)
   │  ├─ question.json              # ⭐ 題庫單一來源
   │  ├─ question.schema.md         # ⭐ 題庫 schema 文件
   │  ├─ types/question.ts          # ⭐ 前後端共用型別
   │  ├─ db.ts / auth.ts
   │  └─ routes/auth.ts / scores.ts / admin.ts
   ├─ package.json / tsconfig.json
   └─ data/users.db(自動建立)

⭐ = v3 新架構核心(題庫集中後端)


改題目 / 詞彙 / 配分

所有遊戲內容集中在 server/src/question.json,改完跑 validator 重開 server 即生效:

# 1. 編輯題庫
vim server/src/question.json

# 2. 驗證(結構 + crossRef + kebab id,中文錯誤訊息)
npm run validate:questions

# 3. 重開後端
cd server && npm run dev

完整 schema 與常見操作(加 vocab、調 rubric 配分、出 mission 新題、single+compare 用法)見 server/src/question.schema.md。


老師操作手冊

建學生

  1. 開 admin console:/#/admin/login
  2. admin / 你的密碼登入
  3. 「學生帳號管理」貼 CSV → 批量建立

看成績

  1. 「成績查詢與匯出」
  2. 上方三卡片:Activation / Retention / Pass Rate
  3. 篩學生、點列展開看 ISBAR 20 項明細
  4. 「匯出 CSV」全班成績

重設密碼

  1. 「學生帳號管理」→ 該列「重設密碼」