適用:使用者想「登記」「留下紀錄」「知道誰填了什麼」,例如器材借用登記、交班待辦、意見回報。
資料存在平台資料服務(UberTeach.data.*),不需要自己架資料庫,也不需要 migration。
什麼時候用這個 skill
- 需要保存同仁填的內容,事後要查、要匯出。
- 需要知道是誰填的:平台會自動蓋章(
created_by),不要自己做登入或自己記姓名。 - 若只是查詢或計算、不留紀錄,改用
static-app。 - 若會存到病人資料:不可以上平台(三級)。存員工個資(如姓名之外的人事資訊、員編)會升為二級,請回到分級問診。
二級也可以用這份做法(2026-10-01 Gary 決定,第 182 項),skills_used 照樣填 form-app;只是二級目前只能有試用版(看 /platform/limits 的 levels[].production),開放程度最多「院內同仁」。
做法
- 建立應用時
archetype選A;manifest 的data.collects如實列出存了哪些欄位(例如[borrower_unit, item, return_date]),skills_used填form-app。 - 載入身分 SDK,先
UberTeach.me(),再用UberTeach.data.save()存、list()查、update(id, data)改、remove()刪。
要改一筆就用 `update()`,不要刪了再加——那樣會把「誰先寫的」換成改的人。
- 一筆紀錄是一包 JSON,上限 64 KB;每個應用預設 10,000 筆、20 MB。超過會回
quota_exceeded,畫面要顯示訊息,不要讓工具整個壞掉。 visibility只能是private、listed或hospital:開放給院外或未登入者的應用不能存資料。- 「只看自己的」用
UberTeach.me()的user_id對created_by.user_id過濾;沒宣告角色時,同一個應用內所有登入者都讀得到全部紀錄,設計時要考慮這點。
使用者說「只有某些人能看/能改」(例如「只有組長能核可」「大家只能改自己填的」),在 manifest 宣告 `roles` 與 `access`(llms.txt §4.7「誰能做什麼」),
平台會在每次讀寫時擋;只給名單上的人用就把 visibility 設成 listed。畫面用 me.app_can 決定顯示哪些按鈕——那是方便,不是防線。
- `list()` 一次最多回 50 筆(回應帶
page、page_size、total)。要統計、要找「哪一天沒人填」、要列出全部時,
一直讀到最後一頁(骨架的 listAll())。只讀第一頁不會報錯,只會無聲地少算——紀錄一多,「漏填」的判斷就錯了。
- 同仁填的內容一律用 `textContent` 放上畫面,不要塞進 `innerHTML`。別人填的一段 HTML 會在每個打開工具的人的瀏覽器裡執行。
骨架
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>器材借用登記</title>
</head>
<body>
<h1>器材借用登記</h1>
<form id="f">
<label>器材 <input name="item" required></label>
<label>預計歸還 <input name="return_date" type="date" required></label>
<button type="submit">登記</button>
</form>
<ul id="list"></ul>
<script src="https://platform.wanfangai.tw/sdk/v1.js"></script>
<script>
// list() returns at most 50 records a page; read them all, or totals are silently short.
async function listAll() {
const all = [];
for (let page = 1; ; page += 1) {
const res = await UberTeach.data.list({ page });
all.push(...res.records);
if (res.records.length === 0 || all.length >= res.total) return all;
}
}
async function refresh() {
const list = document.getElementById('list');
list.replaceChildren();
for (const r of await listAll()) {
const li = document.createElement('li');
// textContent, never innerHTML: what a colleague typed must not run as code for everyone else.
li.textContent = `${r.data.item}|${r.data.return_date}|${r.created_by.display_name}`;
list.append(li);
}
}
document.getElementById('f').addEventListener('submit', async (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.target));
try {
await UberTeach.data.save(data);
e.target.reset();
await refresh();
} catch (err) {
alert(err.message); // 平台回的中文訊息可直接顯示
}
});
UberTeach.me().then(refresh);
</script>
</body>
</html>存成 PDF
使用者要把紀錄清單或某一筆紀錄印出來、或存成檔案交出去時,用瀏覽器的「列印 → 另存為 PDF」,不用套件(jsPDF 沒有中文字型,不要用)。
2026-10-15 用 Chrome 實測:書架的中文字型會嵌進 PDF、長表格跨頁時表頭會重複、圖表完整。
- 放一顆按鈕:
<button type="button" class="no-print" onclick="window.print()">存成 PDF</button>,
旁邊一句「在列印視窗的『目的地』選『另存為 PDF』;不要日期與網址就取消勾選『頁首及頁尾』」。
- 在
<style>最後加列印樣式——選單、按鈕不印,表格的列不切半、每頁重複表頭,圖不超出紙張:
```css
@media print {
@page { size: A4; margin: 15mm; }
nav, button, .no-print { display: none !important; }
body { padding: 0; font-size: 11pt; color: #000; background: #fff; }
h1, h2, h3 { break-after: avoid; }
tr, img, canvas, figure, .card, .chart { break-inside: avoid; }
thead { display: table-header-group; }
table { width: 100%; }
canvas, img { max-width: 100% !important; height: auto !important; }
- { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
}
```
- 只想印某一塊(例如一筆紀錄、某個篩選後的清單):其他區塊加
class="no-print"。 - 想讓每台電腦印出來的字都一樣,用書架的黑體:
<link rel="stylesheet" href="https://platform.wanfangai.tw/lib/noto-sans-tc@5.3.1/400.css">,font-family: 'Noto Sans TC', system-ui, sans-serif(粗體再載700.css)。
發佈前檢查
- 有「存成 PDF」的:在瀏覽器按一次看預覽——中文正常、按鈕與選單沒印出來、表格沒切半、圖完整。
- 在有輸入框的地方提醒:請勿輸入病人資料。
data.collects與畫面實際存的欄位一致,CI 會比對。- 負責人可在應用管理頁把資料匯出成 CSV,告訴使用者這件事。