diff --git a/src/components/common/forge/Forge.vue b/src/components/common/forge/Forge.vue index aadeea3..e5c0ba4 100644 --- a/src/components/common/forge/Forge.vue +++ b/src/components/common/forge/Forge.vue @@ -460,6 +460,28 @@ const { __staticDevices, } = useForgeSprite(); +// === Date 工具 === +function __fmtYMD(d) { + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${y}/${m}/${day}`; +} + +// 依床位與索引產生「穩定且分散」的入住日期(不會落到未來) +function seededStartTime(bedKey, idx = 0) { + const n = parseInt(String(bedKey).replace("-", ""), 10) || 0; + const base = new Date(2024, 0, 1); // 2024/01/01 + const offset = (n * 13 + idx * 17) % 540; // 約 18 個月範圍 + let d = new Date(base.getTime() + offset * 86400000); + const today = new Date(); + if (d > today) { + const back = (n % 60) + 10; // 至少往回 10 天 + d = new Date(today.getTime() - back * 86400000); + } + return __fmtYMD(d); +} + /** ===================== 常數 / 主題色 ===================== */ const FLOOR_DBIDS = { "1F": null, "2F": null }; const BRAND_RED = "#FF8678"; // 女(佔床) @@ -486,6 +508,7 @@ function loadResidentStore() { return new Map(); } } + function saveResidentStore(map) { try { localStorage.setItem( @@ -521,7 +544,7 @@ function ensureResidentFor(bedKey, genderHint) { residentsName: fixed.name, residentsSex: fixed.sex || genderHint || "男", residentsAge: fixed.age ?? 78, - startTime: fixed.startTime ?? "2023/01/15", + startTime: fixed.startTime ?? "2025/01/01", healthStatus: fixed.healthStatus ?? "一般", medicationStatus: fixed.medicationStatus ?? "規律服藥", specialEvent: fixed.specialEvent ?? "-", @@ -532,14 +555,14 @@ function ensureResidentFor(bedKey, genderHint) { return rec; } - // 🧰 再看 localStorage(沒有寫死表才用舊值;補齊性別) + // 再看 localStorage(沒有寫死表才用舊值;補齊性別) const existed = RESIDENT_BY_BED.get(key); if (existed) { if (!existed.residentsSex && genderHint) existed.residentsSex = genderHint; return existed; } - // 🆘 最後備援(真的沒定義到的床位) + // 最後備援(真的沒定義到的床位) const g = genderHint === "女" ? "女" : "男"; const pool = g === "男" ? NAME_POOL_MALE : NAME_POOL_FEMALE; const idx = hashFNV1a(key) % pool.length; @@ -548,7 +571,7 @@ function ensureResidentFor(bedKey, genderHint) { residentsName: pool[idx], residentsSex: g, residentsAge: 78, - startTime: "2023/01/15", + startTime: seededStartTime(key, idx), healthStatus: "一般", medicationStatus: "規律服藥", specialEvent: "-", @@ -1156,6 +1179,49 @@ window.FORGE_API = { }, }; +// === 新增:聚焦指定床位並顯示 popover === +window.FORGE_API.focusBed = async function focusBed(bedKey, opts = {}) { + try { + if (!viewer || !__staticDevices?.length) return false; + + const bed = String(bedKey || "").trim(); + if (!bed) return false; + + // 確認樓層,必要時切換(1xx→1F,2xx→2F) + const targetFloor = bed.charAt(0) === "2" ? "2F" : "1F"; + if (activeFloor.value !== targetFloor) { + await applyFloor(targetFloor); + } + + // 找到對應 sprite + const dev = __staticDevices.find( + (d) => d.bedKey === bed || d.name?.includes(bed) + ); + if (!dev) return false; + + // 讓 popover 篩選顯示到它(selectedInfo 要對上該 sprite 的 status) + selectedInfo.value = dev.status; // 'occupied' | 'vacant' | 'hospitalized' | 'leave' + soloSpriteId.value = dev.spriteDbId; + bringToFrontById(dev.spriteDbId); + + // 重新算 popover 位置 + rebuildLabelsAfterNextRender(); + + // 視角帶到該 sprite(可用 opts.fit=false 關掉) + if (opts.fit !== false) { + await cardfitToView({ + forge_dbid: dev.forge_dbid, + spriteDbId: dev.spriteDbId, + back: 30, + }); + } + return true; + } catch (err) { + console.warn("[FORGE_API.focusBed] failed:", err); + return false; + } +}; + /** ===================== Sprites 顏色規則 ===================== */ function spriteColorBy(status, gender) { if (status === "occupied") return gender === "男" ? BRAND_GREEN : BRAND_RED; @@ -1508,6 +1574,39 @@ async function onClickFloor(next) { /** ===================== 生命週期 ===================== */ onMounted(async () => { + (function migrateResidentStore() { + let changed = false; + for (const [k, v] of RESIDENT_BY_BED.entries()) { + // 只清理舊備援資料或 startTime 落在 2023/01/15 的 + if (!v || v.startTime === "2023/01/15") { + const fixed = RESIDENTS_BY_BED?.[k]; + if (fixed) { + RESIDENT_BY_BED.set(k, { + roomGender: fixed.sex, + residentsName: fixed.name, + residentsSex: fixed.sex, + residentsAge: fixed.age, + startTime: fixed.startTime, + healthStatus: fixed.healthStatus, + medicationStatus: fixed.medicationStatus, + specialEvent: fixed.specialEvent, + note: fixed.note, + }); + } else { + // 沒有寫死表就用新的 seededStartTime + const g = v?.residentsSex || "男"; + RESIDENT_BY_BED.set(k, { + ...v, + startTime: seededStartTime(k, 0), + residentsSex: g, + }); + } + changed = true; + } + } + if (changed) saveResidentStore(RESIDENT_BY_BED); + })(); + document.addEventListener("click", onClickOutsideInfo); document.addEventListener("keydown", onKeydownInfo); activeFloor.value = resolveInitialFloor(); diff --git a/src/constants/mocks/facilityData.js b/src/constants/mocks/facilityData.js index e9ce953..7f020e3 100644 --- a/src/constants/mocks/facilityData.js +++ b/src/constants/mocks/facilityData.js @@ -7,6 +7,7 @@ * @property {string} medicationStatus * @property {string} specialEvent * @property {string} note + * */ // ========== 固定姓名池(A 機構床號 74 → 男/女各 37)========== @@ -233,6 +234,31 @@ export function ageFromBedKey(key, base = 66) { } // ========== 建立固定住民表 ========== +// 工具:格式化成 YYYY/MM/DD +function fmtYMD(d) { + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${y}/${m}/${day}`; +} + +// 工具:依床位與索引產生「穩定且分散」的入住日期 +// 規則:以 2025/01/01 為起點,往後偏移 0~540 天(約 18 個月) +// 若超過今天,會往回扣 (n % 60 + 10) 天,確保不會是未來日期 +function seededStartTime(bedKey, idx) { + const n = parseInt(String(bedKey).replace("-", ""), 10) || 0; + const base = new Date(2025, 0, 1); // 2025/01/01 + const OFFSET_DAYS = (n * 13 + idx * 17) % 540; // 分散 18 個月內 + let d = new Date(base.getTime() + OFFSET_DAYS * 86400000); + + const today = new Date(); + if (d > today) { + const back = (n % 60) + 10; // 至少退 10 天,最多 ~70 天 + d = new Date(today.getTime() - back * 86400000); + } + return fmtYMD(d); +} + function buildFixedResidents(keys, names, sex) { /** @type {Record} */ const out = {}; @@ -243,7 +269,7 @@ function buildFixedResidents(keys, names, sex) { sex, age: ageFromBedKey(k, sex === "女" ? 65 : 66), healthStatus: HEALTH_STATUS_POOL[n % HEALTH_STATUS_POOL.length], - startTime: "2023/01/15", + startTime: seededStartTime(k, i), // ← 用分散演算法產生 medicationStatus: "規律服藥", specialEvent: "-", note: "-", @@ -559,5 +585,41 @@ export const BASE_FACILITY_DATA = { }, }; +// ====== Nursing 待辦:與 Forge 對齊的示範假資料(用 RESIDENTS_BY_BED 反查姓名) ====== +/** @typedef {{date:string, bed:string, name:string, type:string, desc:string}} NursingTodoSeed */ + +export const NURSING_TODOS_SEED = Object.freeze( + (() => { + const getName = (bed) => RESIDENTS_BY_BED?.[bed]?.name || "-"; + + /** @type {NursingTodoSeed[]} */ + const seeds = [ + // (a) 你指定的三筆,姓名會由 RESIDENTS_BY_BED 對應床位自動帶入,確保與 Forge 一致 + { + date: "2025/09/21", + bed: "201-1", + name: getName("201-1"), + type: "一般", + desc: "轉換床位", + }, + { + date: "2025/09/19", + bed: "105-1", + name: getName("105-1"), + type: "一般", + desc: "情緒問題、Alb偏低", + }, + { + date: "2025/09/20", + bed: "207-3", + name: getName("207-3"), + type: "緊急安置", + desc: "適應輔導、衛教需求", + }, + ]; + return seeds; + })() +); + // 兼容性輸出(有些檔案以 FACILITY_DATA 讀取) export const FACILITY_DATA = BASE_FACILITY_DATA; diff --git a/src/pages/nursing/index.vue b/src/pages/nursing/index.vue index 67549a8..bc834a0 100644 --- a/src/pages/nursing/index.vue +++ b/src/pages/nursing/index.vue @@ -1,6 +1,6 @@