1. 選擇填表方式
| 方式 | 設定 | 網站需要開發甚麼 |
|---|---|---|
| 報名連結 | 建立入口,開放新報名,複製網址 | 放一個報名按鈕即可 |
| iframe | 複製「嵌入網站」的代碼 | 在 HTML 放入 iframe,按版面調整高度 |
| 自訂 UI | 在「自訂填表 → 開發者設定」啟用 API,保存一次顯示的金鑰 | 後端保存金鑰及每宗申請的私人憑證;前端按問題和允許操作顯示 |
三種方式可以同時使用,資料都進入此流程的「申請進度」。公開入口每次開始建立新申請;跨介面繼續同一宗申請,必須沿用原私人憑證,不能再呼叫一次新的開始請求。
iframe 使用完整填表頁,框內捲動,沒有自動高度協議。它不需要管理員登入或 API 金鑰。原有獨立活動表單、電郵、QR、簽到及網址維持原來用法。
表單、流程及版本的關係
進入流程的「流程設計」,新增「填寫表單」步驟後,直接在「表單欄位」建立問題,或選「使用已有表單」。
- 流程內表單:題目在流程編輯,和流程一起儲存及發佈。
- 引用既有表單:需要有來源表單管理權限。題目用同一套編輯器,在「編輯來源題目」開啟原表單;回到流程「檢查更新」並明確更新草稿。來源改動後未更新草稿,伺服器會阻止發佈。
- 固定版本:發佈後的問題與規則固定。新版本不會改寫舊申請或自動更換既有報名入口的版本。新版本要建立相應入口,再按需要關閉舊入口的新報名。
- 表單列表:預設顯示獨立及既有表單。切換「全部(含流程版本)」可找到內部版本,點擊名稱返回所屬流程。
引用只共用題目;每宗流程申請有自己的答案。它不會把答案另存進來源活動,也不觸發来源活動的確認電郵、QR 或簽到。來源活動仍按原有方式獨立運作。若要獨立修改流程題目,可「解除引用」,保留目前題目後在流程內編輯。
題型相容性會在選取時顯示,不能默默丟掉不支援的設定。測驗評分、圖片展示與圖片專用上傳設定不直接轉為流程欄位;流程可另外使用私人附件。檔案在流程使用私人上傳識別碼,不能沿用獨立表單的公開檔案 URL。包含個人資料的問題需要工作區應用已允許收集個人資料。
來源表單的引用及發佈由有權限的管理員操作;應用管理金鑰不能用引用繞過來源表單權限。外站自訂 UI 只讀取已發佈的問題,毋須解析 form_source 或建立第二份表單。
2. 金鑰與私人憑證
流程金鑰以 odb_flow_live_ 或 odb_flow_test_ 開頭,只能在 /api/workflow-ui 使用,綁定一個入口及其固定版本。它不能編輯流程、列出全部申請、讀其他流程、分配角色或執行管理員審批。不要把它當作既有的應用管理金鑰。
金鑰只放在網站後端的環境變數 OPENDASHBOARD_WORKFLOW_KEY,不能放在 HTML、iframe、前端程式或網址。網站後端必須核對登入使用者與申請的關係;免登入網站則需要安全的私人連結或伺服器工作階段,不能按姓名、電郵或任意申請編號認領資料。
開始前,網站後端以加密安全隨機數產生 32 bytes 的 base64url 私人憑證(43 字元),持久保存後才送請求。每宗申請一個,後續全部請求使用同一憑證。不要把別人的憑證送給目前訪客。
| Header | 用途 |
|---|---|
Authorization: Bearer <workflow-key> | 此流程入口的伺服器金鑰 |
X-ODB-Participant-Token: <participant-token> | 開始及操作同一宗申請;取得問題時不需要 |
Content-Type: application/json | POST JSON |
3. 取得問題、開始及讀取申請
下載流程後端範例,使用 Node.js 22.13 或更新版本。它只提供呼叫及驗證函式,沒有自動建立申請、開放報名或保存金鑰。與它一起部署到你的網站後端,不能直接放進前端 bundle。
先在 UI 建立一個獨立驗收流程:「須知 → 報名表 → 作品表 → 完成」。表單問題 key 以下僅為示例,實際提交必須使用 definition 回傳的 key。勾選允許「使用自己網站的填表介面」,發佈、建立入口、啟用 API;驗收入口可暫時開放,正式入口保持關閉。open:false 時仍能讀問題,但不能建立新申請。
import { randomUUID } from 'node:crypto';
import { createWorkflowClient, newParticipantToken, startBody } from './workflow-client.mjs';
const client = createWorkflowClient(); // 從伺服器環境讀 OPENDASHBOARD_WORKFLOW_KEY
const definition = await client.definition();
// 展示 definition.introduction 的頁面;consent 必須由本人明確確認。
// checkedByUser 來自本人操作,不可自動全部勾選。
const requestBody = startBody(definition, checkedByUser);接到你的網站儲存層後才執行以下操作。 binding 代表網站自己的持久資料,不是 OpenDashboard 函式或可直接從瀏覽器傳入的物件:
1. 後端驗證登入或私人工作階段。 2. 在資料庫以「該使用者+入口」建立唯一綁定,產生 newParticipantToken() 並加密保存;併發請求必須取得同一綁定。若產品允許多宗申請,再加入你自己的穩定申請代碼。 3. 保存入口、金鑰設定的參照、私人憑證、原始 requestBody。重試或換裝置讀回原值;不要每次產生新憑證。 4. 然後呼叫 start,成功後保存 id;結果未知時用原憑證及原正文重試。
// binding.token、binding.startBody 必須已按上面步驟持久保存。
const started = await client.start(binding.token, binding.startBody);
// started.id 是申請編號;private_url 是相對路徑,例如 /w/<private-token>。
// 僅向該申請的授權使用者提供:new URL(started.private_url, trustedOrigin).href。
let current = await client.application(binding.token);GET ?action=definition 直接回傳物件,沒有 data 包裝:entry、workflow_id、title、version、test、open(布林)、link_days、attachments、forms、information_pages、introduction、resources。每份 forms 包含 id(步驟 id)、form_id、title、questions、submission。
GET ?action=application 直接回傳 id、version、revision、state、definition、current_actions。目前步驟是 state.current;以它找 definition.nodes 中的節點。state.forms[stepId].items 才是該步已保存的項目,尚未開始的步驟可能不存在,請使用 ?.items || []。每項的 id 用於修改時傳入 item,答案在 answers,status 分草稿及已提交。
開始後以 application 的固定版本及 current_actions 為準,不能以最新草稿或固定第幾頁判斷下一步。題型大致沿用表單 UI 參考,但流程私人附件型別是 file,答案是 apu_ 上傳編號;普通表單的 file_upload URL 格式不能搬到流程。不要把管理欄位、整份未篩選定義或其他人的資料直接交給瀏覽器。
4. 兩份表單串成一宗申請
例如流程設計為「隊伍資料 → 作品資料 → 資料確認」。參加者先提交隊伍資料,然後讀取新狀態,再填作品資料。管理員在同一宗申請查看兩步答案。
const command = {
operation_id: randomUUID(),
revision: current.revision,
action: 'submit',
node: current.state.current,
answers: { team_name: '示範隊伍' }
};
// command 要持久保存;逾時時原樣重試,包括 operation_id 及 revision。
const result = await client.action(binding.token, command);
// 保存 result.item;草稿後提交/編輯同一項目必須傳 item。
current = await client.application(binding.token);
if (current.current_actions.includes('continue')) {
const nextCommand = {
operation_id: randomUUID(), revision: current.revision,
action: 'continue', node: current.state.current
};
// 呼叫前持久保存 nextCommand,結果未知時原樣重試。
await client.action(binding.token, nextCommand);
current = await client.application(binding.token);
}欄位 key 以你在流程建立的 questions 為準。save 保存草稿,submit 正式提交。修改已有項目需帶該項目的 item,不可當成新項目。單次及多次提交步驟都一樣,只有 current_actions 包含 continue 時才顯示繼續;提交與繼續是兩個動作。使用同一私人憑證讀回下一步,無須再建立 run。
每個動作都要帶新的 operation_id、最新 revision、action 及 node;精確重試除外。流程角色、期限、必填、數量及分支都由伺服器檢查。參加者不能提交 actor 或冒充管理員。
5. 附件
附件沿用私人流程頁的檔案驗證與歸屬規則:
| 呼叫 | 方法及內容 |
|---|---|
?action=upload | POST {external_id, field, name, content_type, size, sha256};取得上傳位置 |
| 上傳檔案 | PUT 回傳的 upload_url;Content-Type 為實際類型,x-upsert: false;只送原始 bytes,不帶流程金鑰 |
?action=complete-upload&file=<upload-id> | POST {};確認完成 |
| 提交答案 | 在該步的附件欄位填 upload id |
?action=file&file=<upload-id> | GET;取得目前申請可查看的附件 |
只有 OpenDashboard API 步驟帶流程金鑰和私人憑證;儲存空間 PUT 不帶兩者。external_id 用 4–64 字元的英數、底線或連字號;同一檔案重試使用相同 id。不可提交別隊附件。題目使用私人附件時,應用須已開啟 identified mode;配置詳見檔案 API 參考。
field 為目前步驟的附件路徑,例如 document 或 members.photo(不帶陣列索引)。建議一律傳入;有欄位限制時不可省略。完成上傳還未保存答案,必須把回傳 id 放入 save/submit 的答案。
// bytes 為後端接收及檢查過的 Buffer;externalId 在第一次上傳前持久保存。
const uploaded = await client.upload(binding.token, {
externalId: storedUploadId, field: 'document', name: 'proposal.pdf',
contentType: 'application/pdf', bytes
});
// current 是此步最新狀態;answers 包含本步其他完整答案,已有項目另帶 item。
const fileCommand = {
operation_id: randomUUID(), revision: current.revision,
node: current.state.current, action: 'save',
answers: { ...answers, document: uploaded.id }
};
// 先持久保存 fileCommand,再呼叫;修改已有草稿時須加入原 item。
await client.action(binding.token, fileCommand);
// 保存成功且附件已在本申請可見答案中引用後,才可取得下載網址:
const download = await client.file(binding.token, uploaded.id);
// download.url 有效 60 秒。只保存 upload id,不保存短效網址。上傳結果未知時,先用相同 external_id 和 metadata 取得原預約,再嘗試完成驗證;檔案可能已存在,PUT 失敗不一定代表原上傳失敗。範例會呼叫 complete-upload 核對 bytes 的大小及 SHA-256,驗證失敗才回錯誤;更換檔案則用新的 external_id。name、size、sha256 的規則見私人附件合約。流程有附件規則時,保存答案還會驗證檔頭類型與總量;這不是病毒掃描或完整 PDF 安全檢查。
6. 停止、版本及錯誤處理
| 操作/情況 | 實際影響 |
|---|---|
| 停止新報名 | link、iframe、API 不接受新申請;已建立的私人申請仍可繼續 |
| 停用 API | 此金鑰不能讀取或提交;公開連結、iframe 和已發出的私人連結保持原狀 |
| 重新產生金鑰 | 舊金鑰立即失效;申請和私人憑證保留。需更新網站後端 |
| 發佈新版本 | 舊入口和申請保留原版本。建立新版本入口後,替換網站的連結或金鑰 |
| 網路逾時 | 結果未知。開始請求使用同一私人憑證,動作使用原 operation_id 及原內容重試 |
run_changed | 先讀最新申請,保留本地輸入讓使用者核對,不能盲目覆寫 |
invalid_workflow_key | 金鑰不正確、已停用、更換,或應用已停用 |
link_not_found | 私人憑證不屬此入口、過期或已撤銷 |
entry_closed | 尚未開放、已截止或停止新報名 |
invalid_answers | 依 fields 路徑顯示錯誤,保留輸入;修正後用新 operation_id 提交 |
operation_id_reused | 同一操作編號被用於不同內容;查核原操作,新動作使用新編號 |
invalid_upload_field / invalid_upload / upload_content_mismatch | 核對欄位路徑、類型、大小及檔案 bytes |
window_closed | 該步驟已截止或尚未開放;與入口是否接受新申請是兩項設定 |
custom_ui_not_allowed | 此入口的已發佈版本不允許自訂 UI;在流程重新設定並發佈新版本 |
action_not_allowed | 目前步驟或角色不允許此操作 |
rate_limited | 按 Retry-After 等待後,原樣重試 |
API 回傳 {ok:false,error,message,fields} 錯誤格式,fields 只在有欄位錯誤時提供。只有成功回覆才顯示「已保存/已提交」。金鑰設定遇到未知結果,重新載入狀態;若未取得一次顯示的金鑰,重新產生後再接入。
7. 範圍
這是參加者填表 API,管理員在 OpenDashboard「申請進度」查看及確認資料。此參加者金鑰沒有全部申請匯出權限。外站副本可另用流程資料同步的應用金鑰讀取與變更通知;不提供自動雙向同步、評分或排名。舊式應用流程 API保留原合約,權限較廣;新網站填表優先使用本頁的流程金鑰。
服務狀態與操作紀錄
流程發佈後會開啟「網站接入」。日常管理不需要選擇資料環境;既有測試流程仍保持隔離,並標示「測試流程」。頁面分成「報名連結」、「自訂填表」及「資料通知」三區。報名連結區可預覽、複製及展開嵌入代碼;金鑰及程式範例收在「自訂填表 → 開發者設定」。各區的 ⓘ 可點擊展開普通文字說明,手機及鍵盤也可使用。
- 服務中 / In Service:此入口的自訂填表 API 已啟用,不代表已驗證外站健康或最近請求成功。新報名是否開放另行顯示。
- 成功/失敗:最近 24 小時已通過流程金鑰驗證的 API 操作,HTTP 小於 400 算成功,其餘算失敗。讀取、保存、上傳各算一次,並非報名人數。
- 自訂填表 → 使用紀錄:查看最近 7 天、每頁 20 筆,可只看失敗、手動重新整理及展開詳細紀錄。公開報名連結與 iframe 的申請在「申請進度」查看,不計入此 API 紀錄。
- 無效或已停用的金鑰無法安全歸屬流程,不納入紀錄。紀錄在回應完成後盡力保存,可能短暫延遲;服務中斷時可能缺漏,不是計費或完整稽核依據。
- 只保存操作類型、時間、狀態、錯誤碼及耗時,不保存金鑰、填表答案、請求內容或檔案名稱。介面只讀取最近 7 天;背景工作分批清除較早紀錄。
- 資料通知 → 傳送紀錄:另一組紀錄,表示資料保存後通知外站是否送達,不代表填表是否成功。
查看操作紀錄需要工作區的「查看個人資料」權限。接入設定只供具備相應管理權限的使用者操作。
資訊頁、參考資料與同意確認
在「流程設計 → 新增步驟 → 資訊頁」建立內容。中央編輯文字、標題、清單、連結、圖片及 PDF,右側設定「流程步驟」或「只供查閱的參考資料」。流程步驟可同時列於參考資料;兩個位置使用同一份內容。資訊頁不建立表單或回覆,也不列在表單管理。
資訊頁與流程一起儲存及發佈。已開始的申請固定原版本;既有報名入口也不會自行切換版本。上傳的資訊頁圖片/PDF 是公開材料,不是參加者的私人附件;請勿放入個人資料。接受 JPG、PNG、WebP 及 PDF,每檔最多 3 MB,圖片會縮圖及移除 metadata。PDF 提供獨立預覽和下載,手機不嵌入狹小文件框。移除區塊不會刪除舊版本仍引用的附件。
GET ?action=definition 新增以下欄位(既有 forms 不變):
| 欄位 | 內容 |
|---|---|
information_pages | {id,title,blocks,placement,show_in_resources,button_label,consent,next?} |
introduction | 開始填表前依次顯示的資訊頁 id 陣列;從流程起點連續的資訊頁取得 |
resources | 可隨時查閱的資訊頁 id 陣列;打開不改變進度 |
blocks 為有序陣列:{id,type,text};type 包含 paragraph、heading、subheading、bullets、numbered、link、image、pdf。清單的 text 每行一項。連結另有 url;圖片/PDF 另有 url,name,size。請當純文字渲染,不把 text 當 HTML;連結只接受 http/https。圖片的 text 作圖片說明/替代文字。
開始前的瀏覽不建立申請。 網站先展示 introduction,最後由參加者明確點擊開始。consent 非空時,必須展示未預先勾選的確認欄位;單純開啟頁面、PDF 或「繼續」不代表同意。
// checkedByUser 必須來自本人實際勾選;不可直接將所有項目設成 true。
const information = definition.introduction.map(id => ({
id,
accepted: checkedByUser[id] === true
}));
// 先持久保存這份正文和 binding.token,再呼叫:
await client.start(binding.token, { version: definition.version, information });伺服器核對完整次序、入口固定版本及必要確認,才建立同一宗申請,並跳到首個非資訊頁步驟。version 是確認你顯示的內容版本,不允許選用另一版本。無前置資訊頁的舊流程仍可送 {}。逾時時保存同一私人憑證及原請求重試。information_changed 表示應重新讀取並展示內容;information_confirmation_required 表示缺少確認。
填表期間遇到 type: "information" 的步驟,按 current_actions 執行正常流程操作:
await client.action(binding.token, {
node: current.state.current,
action: 'continue',
accepted: checkedByUser[current.state.current] === true,
revision: current.revision,
operation_id: randomUUID()
});只有所屬申請的參加者/聯絡人或有權限的主辦方可繼續。state.information 記錄 {node,at,version,accepted};accepted:false 表示只是按繼續,不能宣稱使用者同意。實際內容由該申請不可變的 definition 取得。主辦方可在申請進度查看確認紀錄;外站的申請 export 也包含 information。OpenDashboard 連結、iframe 及自訂 UI 使用同一份已發佈內容與驗證規則。
在管理介面設定附件規則
管理員在「流程設計 → 填寫表單 → 表單欄位」選擇附件題目,使用「附件限制」設定接受的 JPG/PNG/PDF 及每檔大小(平台上限 50 MiB)。重複群組內的附件使用相同設定。在「流程設定」可選填整宗申請的附件總大小。空白表示沒有另加流程總額限制;平台既有上傳限制仍適用。
規則隨草稿儲存、隨版本發佈,不能直接改寫既有申請。正式啟用前,須與接收網站支援的格式和容量核對;OpenDashboard 的設定不會自動擴充外站儲存能力。
平台容量提升不會重寫既有流程:未設定 upload 的舊欄位仍採 20 MiB;要使用 50 MiB,管理員需在欄位設定限制、儲存及發佈新版本,再讓新入口使用該版本。既有申請保持原版本。外站接收端也必須能處理相同大小。
已發佈附件欄位可含 upload: {content_types: ["application/pdf"], max_bytes: 20971520};流程 definition 可含 attachments: {max_total_bytes: 52428800}。總大小涵蓋同一宗申請所有表單項目的附件;同一檔案在兩個欄位使用也計兩次。
自訂 UI 應直接用這些規則顯示提示、限制選檔,不能另藏一套規則。upload 請求增加 field,是目前步驟的欄位路徑,如 members.photo,不包含行號。當目前步驟有設定附件限制時,必須提供此路徑。OpenDashboard 後端在上傳預約檢查格式與大小,在保存/提交檢查附件歸屬、檔案內容格式及全申請總大小;只改副檔名或 HTML 的 accept 不能繞過驗證。未設定規則的舊流程保持相容。
登入綁定仍由網站後端實作:以已驗證帳號為索引,持久保存一份加密私人憑證,建立唯一關聯後才開始申請。網絡結果未知時沿用原憑證重試;瀏覽器傳入的帳號、申請編號或電郵不能作為認領證明。這是外站接駁責任,不是 OpenDashboard 勾選一下便會完成的登入功能。
發佈前確認與權限
在「流程管理 → 流程 → 發佈版本」先確認填表入口及資料副本,再按「確認並發佈版本」。 新流程預設使用 OpenDashboard 報名頁,不啟用自訂網站填表或資料同步。既有流程沒有這份設定時保留原行為。
| 選擇 | 新版本允許的行為 | 發佈後還要完成 |
|---|---|---|
| 使用 OpenDashboard 報名頁 | 從公開連結或 iframe 建立申請 | 建立此版本入口,預覽後開放報名 |
| 使用自己網站的填表介面 | 以流程金鑰及私人憑證填表 | 啟用 API,網站後端保存金鑰、綁定帳號並接入 |
| 通知我的網站,讓兩邊都有資料 | 變更通知、同步匯出及同步附件讀取 | 設定通知接收網址、簽章及後端讀取副本,回報處理結果 |
可同時勾選兩種填表方式;答案、驗證及進度仍由 OpenDashboard 主導。勾選不是接駁成功,也不會自動開放報名或發出金鑰。 在「網站接入」查看所選入口的版本與允許範圍,以及服務啟用、通知送達、外站處理結果。
管理用流程定義的可選 publication 為 {hosted: true, custom_ui: false, data_sync: false}。 三項都必須是布林值,至少一種填表方式須開啟。發佈時保存於不可變版本;修改需發佈新版本及建立新入口,舊申請及入口不受影響。 hosted: false 禁止該入口的公開開始申請,已發出的私人連結仍可用。 custom_ui: false 禁止啟用或使用該版本的流程 UI API,回傳 custom_ui_not_allowed。 data_sync: false 不排入該版本的變更通知,外站同步 export/files 端點回傳 data_sync_not_allowed。
這些是流程接入範圍,不會撤銷既有工作區管理員或連接應用管理金鑰的其他管理能力。 管理員 API 啟用、金鑰及資料通知設定仍須同時持有 edit_boards 與 read_personal。
使用連結或 iframe
選「使用 OpenDashboard 報名頁」並發佈,於「網站接入」建立新版本入口。 先預覽報名頁,確認問題、資訊頁及附件規則,再開放新報名。使用「複製報名連結」或展開「嵌入自己的網站」取得 iframe。 這兩種方法使用同一套 OpenDashboard 畫面,不需要啟用自訂填表 API。
自己的填表介面
選「使用自己網站的填表介面」並發佈,建立新版本入口後,在「自訂填表 → 開發者設定」啟用 API。 流程金鑰只保存在網站後端;使用本頁的 definition → start → application(需要檔案時先 upload)範例接入。 網站後端驗證登入帳號,保存帳號與私人憑證的對應;不要接受瀏覽器提交的申請編號來認領資料。
同時開放 OpenDashboard 入口時,兩邊續填同一宗申請須使用同一個私人憑證及帳號對應。 若兩邊使用不同私人憑證呼叫 start,會建立不同申請;同一憑證的精確重試仍是同一宗;OpenDashboard 不會根據姓名或電郵自動合併。 只需要外站填表及該使用者查看自己的申請,不需要勾選資料副本;需要外站資料庫保存時,再看同步教學。
接入完成怎樣驗收
| 檢查 | 預期結果 |
|---|---|
| 只查看須知,未按開始 | 申請數不增加;缺少必要同意不建立申請 |
| 同一開始請求重試 | 相同 id,只有一宗申請 |
| 保存草稿後提交 | 帶原 item;仍只有一個項目 |
| 兩份表單依次 submit/continue | 同一申請 id,各步有自己的答案;申請可完成 |
| 必填缺漏、錯誤選項、附件超限/錯誤類型 | 失敗並顯示欄位錯誤,不宣稱成功 |
| 原操作精確重試、過期 revision | 前者不重複;後者拒絕覆寫並要求核對 |
| 另一帳戶或另一入口的私人憑證 | 網站後端拒絕帳戶越權;平台拒絕跨入口憑證 |
| 停收後繼續原申請 | 不新增申請,原申請仍依其有效期及步驟期限操作 |
| 外站副本 | 額外核對通知驗證、處理回報、去重及附件落地 |
範例及契約測試使用合成資料;你的登入、儲存層、檔案服務和接收網站仍須按此表在隔離流程驗收。服務中或 HTTP 200 不等於整個網站已驗收完成。