問卷設計器(Survey Designer)
事實基準:2026-07-05 從 FE(
views/survey-v2/SurveyDesigner.vue等)/ BE(api/survey、jedi-survey 套件)code 掃出;表結構取自db_schema.json(surveyschema);error code 對jedi_survey/common/enum/error_code.py
1. 功能描述
問卷設計器是單一問卷的內容編輯器:左欄維護頁面樹(章節),中欄編輯選中頁的題目——題型、選項、權重、必填、子題、流程規則。頂列可儲存、另存新版、發布、預覽。設計器編輯的是作者端範本(is_snapshot=0);範本發布後才能被專案規劃頁的稽核任務綁定消費(綁定取快照,見
_overview §4)。
主要使用者:問卷範本作者(由問卷管理頁點「編輯」進入)。實際 endpoint
僅 @jwt_required(),無角色細分(見 §3)。
角色速覽(作者端以選單能力控管,見 _overview §5):
| 角色 | 一句話 |
|---|---|
| 問卷作者 | 由問卷管理頁進入;本頁全部編輯(endpoint 僅 jwt,無角色細分,見 §3) |
1.1 功能總覽(本頁全部功能)
| # | 功能 | 說明 | 位置 | 詳述 |
|---|---|---|---|---|
| 1 | 載入問卷結構 | GET 問卷含 pages / questions 樹 | 進頁 | §6.2 |
| 2 | 頁面樹 | 遞迴樹、搜尋、選中頁;每頁顯示題數 | 左欄 | §5 |
| 3 | 新增頁面 | 編號 / 排序 / 名稱 / 父頁 / 描述 | 左欄 → AddPageDialog | UC-SVD-04 |
| 4 | 編輯 / 刪除頁面 | 右鍵選單;刪除連子頁 + 題目 CASCADE | 左欄右鍵 | UC-SVD-02 |
| 5 | 頁面排序 / 移動 | 拖動排序、改父頁(不可移到自己底下) | 左欄 | UC-SVD-02 |
| 6 | 題目清單 | 選中頁的題目卡(型別 / 必填 / 權重 / 選項預覽 / 子題) | 中欄 | §5 |
| 7 | 新增 / 編輯題目 | 三步精靈:基本 → 選項 → 進階 | 中欄 → AddQuestionDialog | UC-SVD-01 |
| 8 | 題型 | 7 種:text / textarea / radio / dropdown / checkbox / fileupload / link | 精靈步驟1 | §1 表下 |
| 9 | 選項 + 補充輸入 | 選擇題選項含 label / 描述 / 補充輸入切換 | 精靈步驟2 | §10 |
| 10 | 子題 | 題目掛在母題下(pid) | 精靈步驟3 | §9 |
| 11 | 流程規則 | 選某選項 → 跳某題(FE flowRules) | 精靈步驟3 | §12 |
| 12 | 題目排序 / 複製 / 移動 | 上下移、複製、移到他頁 | 題目卡操作 | UC-SVD-02 |
| 13 | 儲存 | 就地更新(不動版本) | 頂列 [儲存 ▾] | UC-SVD-03 |
| 14 | 另存新版 | 保留舊版、同 group_id 新 version_no | 頂列 [儲存 ▾] | UC-SVD-03 |
| 15 | 發布 / 取消發布 | 切 release |
頂列 | UC-SVD-03 |
| 16 | 預覽 | 開唯讀預覽頁 | 頂列 ↗ | →問卷填寫 / 預覽 |
| 17 | 問卷資訊設定 | 名稱 / 對象 / 有效期 / 發布 / 範本 / 描述 | 頂列 ⚙ dialog | §6.2 |
題型(survey-mock-data.js):text
文字輸入・textarea 多行文字・radio
單選題・dropdown 下拉選擇・checkbox
核取方塊・fileupload 檔案上傳・link 連結。僅
radio / dropdown / checkbox 有選項步驟。
UC(§2)展開本頁所有具實質後端寫入的功能:新增頁面(3, UC-SVD-04)、頁面編輯 / 刪除 / 排序 / 移動與題目排序 / 複製 / 移動(4-5, 12, UC-SVD-02)、新增 / 編輯題目含選項 / 子題(7, 9-10, UC-SVD-01)、儲存 / 另存新版 / 發布(13-15, UC-SVD-03)。問卷資訊設定(17)走 UC-SVD-03 同一
PUT /survey/{uid}存檔路徑,不另立 UC。純載入 / 顯示 / 導覽類(1 載入、2 頁面樹、6 題目清單、8 題型、16 預覽)見 §5 / §6 / 交叉連結;流程規則(11)由 FE 組成、BE 不驗證(§12 坑 6),無後端邏輯故不展開。
每個 UC 對應的 §1.1 功能:UC-SVD-01→7 / 9 / 10、UC-SVD-02→4 / 5 / 12、UC-SVD-03→13 / 14 / 15(含 17 資訊設定存檔)、UC-SVD-04→3。
2. Use Case
角色速覽(作者端以選單能力控管,見 _overview §5):
| 角色 | 一句話 |
|---|---|
| 問卷作者 | 由問卷管理頁進入;本頁全部編輯(endpoint 僅 jwt,無角色細分,見 §3) |
流程圖視覺慣例:菱形 = 判斷、橘底 = 例外 / 擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。
UC-SVD-01 新增 / 編輯題目(三步精靈)
| 項目 | 內容 |
|---|---|
| 角色 | 問卷作者 |
| 前置條件 | 已選中一個頁面 |
| 產出 / 後置條件 | 新增 → POST /survey-question;編輯 →
PUT /survey-question/{uid}(部分更新);選項步驟僅選擇題型出現 |
UC-SVD-02 頁面 / 題目樹操作(移動 / 刪除)
| 項目 | 內容 |
|---|---|
| 角色 | 問卷作者 |
| 前置條件 | — |
| 產出 / 後置條件 | 移頁到自己底下擋 SURVEY_409005;刪頁 = 硬刪除、子頁 +
題目 CASCADE(不可還原);排序 / 移動題目更新 sort / page_id |
UC-SVD-03 儲存 / 另存新版 / 發布
| 項目 | 內容 |
|---|---|
| 角色 | 問卷作者 |
| 前置條件 | — |
| 產出 / 後置條件 | 儲存 = PUT /survey/{uid} 就地;另存新版 =
POST /survey as_new_version 保留舊版、URL 換新 uid;發布 =
PUT .../release 後可被任務選用 |
UC-SVD-04 新增頁面(章節)
| 項目 | 內容 |
|---|---|
| 角色 | 問卷作者 |
| 前置條件 | 已在設計器內(載入了一份問卷) |
| 產出 / 後置條件 | POST /survey-page 新增一列
survey.survey_pages;pid=null
為根頁、指定父頁則掛其下;頁面樹即時插入新節點 |
3. 權限矩陣
作者端角色基調見 _overview §5。本頁實際檢查點:
| 操作 | FE 判定 | BE 強制 | BE 檢查位置 |
|---|---|---|---|
| 進入 / 載入問卷 | route meta.requiresAuth |
@jwt_required() |
api/survey/routes/survey_route.py::SurveyRoute.get |
| 頁面 CRUD / 排序 / 移動 | 選單能力顯示 | 僅 @jwt_required(),無角色守門;移頁擋 409005 |
api/survey/routes/survey_page_route.py(全部僅
jwt) |
| 題目 CRUD / 排序 / 移動 | 選單能力顯示 | 僅 @jwt_required(),無角色守門 |
api/survey/routes/survey_question_route.py(全部僅
jwt) |
| 儲存 / 另存新版 / 發布 | — | 僅 @jwt_required() |
survey_route.py::SurveyRoute.put・SurveyReleaseRoute |
⚠️ 同問卷管理頁 §3 的缺口:設計器所有寫入端點只有 JWT、無角色守門——任何登入者只要拿到問卷 uid 就能改題目結構。收進 §12。
4. 狀態機與前置條件
設計器改的是作者端範本;相關狀態是發布生命週期(surveys.release)。本頁
gate:
| 條件 | 效果 | 出處 |
|---|---|---|
| 移動頁面目標 = 自身或子孫 | 擋 SURVEY_409005(無法移動到自己底下) |
jedi_survey/domain/service/survey_page_domain_service.py:332 |
| 刪除頁面 | 硬刪除;子頁與題目經 FK ON DELETE CASCADE 一併刪 |
survey_pages FK
fk_survey_pages_pid_survey_pages /
..._survey_id_surveys |
| 另存新版 | 保留原版、同 group_id 產生新 version_no
列,前端 URL 換為新 uid |
jedi_survey/app/service/survey.py::add_survey(as_new_version=True) |
| 儲存(PUT) | 就地更新當前版本,不 bump version_no |
SurveyRoute.put |
| 發布 | release=1 → 出現在
/surveys/menu,可被規劃頁任務選用 |
SurveyReleaseRoute |
5. UI 設計
📸 實際畫面截圖待補(一律亮色模式拍攝;截圖另有專責 session)。預計補到
assets/img/: ① 三欄整頁(頁面樹 + 題目卡)designer-overview.png② AddQuestionDialog 三步(基本 / 選項 / 進階)designer-question-wizard.png③ 題目卡(型別標籤 / 選項預覽 / 子題)designer-question-card.png④ 問卷資訊設定 dialogdesigner-info-dialog.png
版面骨架與區塊職責(三欄設計工作區:左頁面樹選中頁 → 中欄載入題目卡,頂列動作+兩個 dialog;彩色區塊可點跳對應小節錨點,API 僅標代表性):
問卷結構模型(設計器操作對象):
狀態呈現:頁面樹用遞迴 PageTreeNode(展開 / 收合 / 選中
/
右鍵);題目卡顯示型別圖示、必填星號、權重與流程規則標籤;選中頁切換時中欄重載題目。無
socket——設計器不即時協作(即時協作只在填寫頁);另存新版後
router.replace 換 URL 到新版 uid。
6. API 規格
Envelope:成功 {"status": true, "data": …}、失敗
{"error_code": "…", "msg": "…"}。
6.1 總清單(本頁呼叫的全部 endpoint)
| 分類 | Method + Path | 說明 | 完整規格 |
|---|---|---|---|
| 載入 | GET /survey/{uid} |
問卷含 pages / questions 樹 | §6.2 |
| 頁面 | POST /survey-page |
建立頁面 | §6.2 |
| 頁面 | PUT /survey-page/{uid} |
部分更新頁面 | §6.2 |
| 頁面 | DELETE /survey-page/{uid} |
刪除頁面(CASCADE 子頁 + 題目) | §6.2 |
| 頁面 | POST /survey-pages/reorder |
同層排序 | GAI-SD-02 |
| 頁面 | POST /survey-page/move |
改父頁(擋 409005) | §6.2 |
| 題目 | POST /survey-question |
建立題目 | §6.2 |
| 題目 | PUT /survey-question/{uid} |
部分更新題目 | §6.2 |
| 題目 | DELETE /survey-question/{uid} |
刪除題目 | GAI-SD-02 |
| 題目 | POST /survey-questions/reorder |
同頁排序 | GAI-SD-02 |
| 題目 | POST /survey-question/move |
移到他頁 | §6.2 |
| 儲存 | PUT /survey/{uid} |
就地更新問卷 | →manage §6.2 |
| 版本 | POST
/survey(as_new_version=true+group_id) |
另存新版 | →manage §6.2 |
| 發布 | PUT /survey/{uid}/release・DELETE
.../release |
發布 / 取消 | →manage §6.2 |
6.2 核心 endpoint(欄位對照 serializer / service,出處標段末)
[POST] /survey-page ・ [PUT] /survey-page/{uid} — 頁面建立 / 更新
建立 Request:
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
| survey_uid | string | 是 | 所屬問卷 UID |
| no | string | 否 | 編號 |
| name | string | 否 | 名稱 |
| description | string | 否 | 描述 |
| sort | int | 否 | 排序 |
| pid | int|null | 否 | 父頁 DB id,null = 根 |
更新為部分更新(只送有改的 no /
name / description /
sort)。頁面不存在擋
SURVEY_404003。出處:api/survey/routes/survey_page_route.py、serializer
api/survey/serializers/survey_page.py。
[POST] /survey-page/move — 移動頁面
Request:
{
"page_uid": "string",
"target_parent_uid": "string" // null = 移到根
}目標 = 自身 / 子孫時擋
SURVEY_409005。出處:survey_page_route.py、jedi_survey/domain/service/survey_page_domain_service.py:332。
[POST] /survey-question ・ [PUT] /survey-question/{uid} — 題目建立 / 更新
建立 Request:
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
| page_uid | string | 是 | 所屬頁面 UID |
| no | string | 否 | 題目編號 |
| question | string | 否 | 題目內容 |
| type | string | 是 | 題型 |
| weights | int | 否 | 題目權重(預設 1) |
| sort | int | 否 | 排序 |
| required | int | 否 | 必填(0/1) |
| pid | int | 否 | 母題 DB id,子題用 |
| options | array | 否 | 選項陣列 |
| description | string | 否 | 說明 |
更新為部分更新(含可改 page_uid
搬頁)。選項 jsonb
每項:{key, name, content, ext_answer, ext_answer_desc}(FE
↔︎ API 對照見 §10)。題目不存在擋
SURVEY_404004。出處:api/survey/routes/survey_question_route.py、serializer
api/survey/serializers/survey_question.py。
[GET] /survey/{uid} — 載入問卷結構
回問卷主檔 + pages(樹)+ questions(含 options / tags / references
jsonb);供設計器渲染。同時回
has_answer。出處:api/survey/routes/survey_route.py::SurveyRoute.get。
問卷資訊設定(⚙ dialog)
改 name / release_target(對象)/
release_at(有效期)/ release(發布切換)/
is_sample(範本切換)/ description → 走
PUT /survey/{uid}(見 manage
§6.2)。version / folder 唯讀。
7. 前端檔案地圖(compliance-manager-fe/)
| 檔案 | 角色 |
|---|---|
src/views/survey-v2/SurveyDesigner.vue |
主檢視(頂列 / 頁面樹 / 題目編輯 / 資訊 dialog) |
src/views/survey-v2/components/PageTreeNode.vue |
頁面樹遞迴節點(展開 / 選中 / 右鍵) |
src/views/survey-v2/components/AddPageDialog.vue |
新增 / 編輯頁面 |
src/views/survey-v2/components/AddQuestionDialog.vue |
三步題目精靈(基本 / 選項 / 進階 + 流程規則) |
src/views/survey-v2/composables/useSurveyApi.js |
API composable(page / question CRUD、儲存、版本) |
src/views/survey-v2/utils/survey-mapper.js |
API ↔︎ FE 欄位轉換(pid↔︎parentId、option key↔︎id) |
src/views/survey-v2/data/survey-mock-data.js |
題型常數(questionTypes / Labels / Icons) |
src/config/router/index.js:719-722 |
route
survey-v2-designer(/survey/designer/:id) |
src/config/api/api.js |
endpoint 常數(SURVEY_PAGE /
SURVEY_PAGES_REORDER / SURVEY_QUESTION /
SURVEY_QUESTION_MOVE …) |
8. 後端檔案地圖(本頁核心鏈路)
| 鏈路 | Route | App Service | 底層 |
|---|---|---|---|
| 載入 | api/survey/routes/survey_route.py::SurveyRoute.get |
jedi_survey SurveyService |
domain / repo |
| 頁面 CRUD | api/survey/routes/survey_page_route.py |
jedi_survey/app/service/survey_page.py |
survey_page_domain_service(move 守門在此) |
| 題目 CRUD | api/survey/routes/survey_question_route.py |
jedi_survey/app/service/survey_question.py |
survey_question_domain_service |
| 儲存 / 版本 | survey_route.py::SurveyRoute.put /
.post(as_new_version) |
jedi_survey/app/service/survey.py |
domain / repo |
DDD 提醒:問卷 domain / repo 全在
jedi_survey 外部套件;主專案 route 只做 HTTP 邊界(解
uid、取 locale、呼叫套件 service)。所有寫入 route 僅
@jwt_required()、未加角色守門(見 §3 /
§12)——新增端點勿比照。
9. DB
9.1 資料表總清單(本頁讀寫的全部表)
| 表 | 讀/寫 | 說明 | 欄位詳述 |
|---|---|---|---|
| survey.surveys | 讀 / 寫(儲存 / 版本 / 發布) | 問卷主檔 | manage §9.3 |
| survey.survey_pages | 讀 / 寫 | 頁面(樹 pid);硬刪除、無 is_delete | §9.3 |
| survey.survey_questions | 讀 / 寫 | 題目(樹 pid、型別、選項 jsonb);硬刪除、無 is_delete | §9.3 |
| survey.survey_pages_trans / survey_questions_trans | 寫 | 翻譯表(含 locale 時) | §9.3 |
9.2 ER 圖(本頁讀寫範圍)
9.3 核心表欄位(取自 DEV DB dump,含 DB comment)
survey.survey_pages — 頁面(樹,12 欄,無 is_delete)
| 欄位 | 型別 | 說明 |
|---|---|---|
| uid | varchar(36) | 頁面 UUID |
| survey_id | int | 所屬問卷(→ surveys.id,ON DELETE CASCADE) |
| pid | int|null | 父頁面(→ survey_pages.id
自參照,ON DELETE CASCADE) |
| no | varchar(30) | 頁面編號 |
| name / description | varchar(255) / varchar(500) | 名稱 / 描述 |
| sort | int|null | 排序 |
survey.survey_questions — 題目(樹,19 欄,無 is_delete)
| 欄位 | 型別 | 說明 |
|---|---|---|
| uid | varchar(36) | 題目 UUID |
| page_id | int | 所屬頁面(→ survey_pages.id,ON DELETE CASCADE) |
| survey_id | int|null | 所屬問卷(冗餘,便於查詢) |
| pid | int|null | 父題目(子題用自參照) |
| no | varchar(30) | 題目編號 |
| question | text | 題目內容 |
| type | varchar(30) | 題型(text / textarea / radio / dropdown / checkbox / fileupload / link) |
| weights | int | 題目權重 |
| required | int|null | 必填(1 / 0) |
| options | jsonb|null | 選項陣列 |
| tags / references | jsonb|null | 標籤 / 參考資料 |
| description | text|null | 題目說明(help text) |
| sort | int|null | 排序 |
翻譯表
survey_pages_trans:survey_page_id /
name / description /
language_code;survey_questions_trans:survey_question_id
/ question / description / tags /
references / options /
language_code。寫入含翻譯欄位要傳 locale(見
_overview §6.3)。
10. 頁面邏輯與資料對應
載入時序:進頁 GET /survey/{uid} →
mapSurveyFromApi 建 pages / questions 樹 → 預設選第一頁 →
中欄渲染題目。
關鍵欄位對應(FE ↔︎ API):
| 概念 | FE | API |
|---|---|---|
| 頁面 / 題目父參照 | parentId(UID) |
pid(DB int id) |
| 頁面編號 / 名稱 / 排序 | code / name / sortOrder |
no / name / sort |
| 題號 / 權重 / 必填 / 說明 | questionNumber / weight /
isRequired / helpText |
no / weights / required(0/1)
/ description |
| 選項 id / 標籤 / 描述 | id / label / description |
key / name / content |
| 選項補充輸入 | hasSupplementInput /
supplementDescription |
ext_answer(bool) / ext_answer_desc |
選項
id ↔︎ key往返:survey-mapper.js用key保存 FEid,避免以 label 當 key 造成碰撞。
儲存流程:題目 / 頁面的每個新增 / 編輯 / 排序 /
移動即時打對應 endpoint(非整批);頂列「儲存」=
PUT /survey/{uid} 整包;「另存新版」=
POST /survey as_new_version 後換 URL。
錯誤對應:SURVEY_404003(頁面不存在)、SURVEY_404004(題目不存在)、SURVEY_409005(頁面移到自己底下)→
toast。
11. 背景行為與外部依賴
| 類型 | 內容 |
|---|---|
| 通知 | 本頁無通知 |
| Job | 無背景 job |
| Socket | 設計器無 socket(useSurveySocket
只用於填寫頁協作);設計器儲存靠即時
REST |
| jedi-* 套件 | jedi-survey(問卷 / 頁面 / 題目 domain / repo /
錯誤碼)、jedi-auth(JWT) |
| 系統參數 | 無 feature flag |
12. 邊界情況與已知坑
- 寫入無角色守門(安全缺口):頁面 / 題目 / 儲存 /
發布所有 route 只有
@jwt_required(),無 manager / 能力層強制(同問卷管理 §12)。任何登入者拿到 uid 就能改題目結構。 - 頁面 / 題目是硬刪除:
survey_pages/survey_questions無is_delete欄,DELETE為真刪除;刪頁經 FK CASCADE 連子頁 + 該頁全部題目一起消失、不可還原。與問卷 / 資料夾的軟刪除(is_delete)語意不同——刪頁前想清楚。 SURVEY_409003(頁面有子頁)是死碼:error code 有定義但無 raise site;刪頁不會因有子頁被擋,而是直接 CASCADE 刪掉子頁。別在 spec / FE 假設「有子頁不能刪」。- 移頁守門只有「自己底下」:
SURVEY_409005只擋移到自身 / 子孫;跨問卷移動等其他非法情境未見額外守門。 - 儲存 vs 另存新版:「儲存」就地覆寫當前版本(不
bump);「另存新版」保留舊版 + 同
group_id新version_no,且前端 URL 換新 uid——編輯中誤按會分岔出新版本列。 - 流程規則存在 FE:選項→跳題的
flowRules由 AddQuestionDialog 組成(option→target 映射),落地在題目的 jsonb(options/ 相關欄位);BE 不驗證流程規則正確性——目標題被刪 / 改編號時規則可能懸空。 - 可翻譯欄位:
question/description/tags/references/options(題目)與name/description(頁面)有_trans表;未傳locale只更新主表,讀取走 failover 優先翻譯表(見docs/claude/translatable-fields.md)。 - 子題與型別:子題(pid)在同頁掛母題下;題型的選項步驟只對 radio / dropdown / checkbox 出現,其餘型別無 options。
13. 開發與驗證
- 跑起來:BE
python main_socketio.py(port 8000,log 在log/app.log);FE 在compliance-manager-fe/起 Vite dev server。BE 改 service code 後必須重啟(無 hot reload) - 測試帳號:dev 環境登入帳號密碼見
.env/ 部署文件 - 導航路徑:問卷管理頁點問卷「編輯」→
/survey/designer/:id - 前置資料:需先有一份問卷(可由問卷管理頁建立空白問卷後進入)
- E2E:
compliance-manager-test/repo(Cucumber + Playwright);以問卷設計/survey designer搜尋 feature 檔 - 相關文件:問卷 CRUD / 版本 / 發布見 問卷管理頁、填答 / 預覽見 問卷填寫 / 預覽;DB / API 全量見 GAI-SD-02/03