專案列表頁(Project List)
事實基準:2026-07-05 從 FE / BE code 掃出(FR-038 v2 round-based 架構);API schema 逐條對照 marshmallow serializer 與 service 組裝碼;表結構取自
db_schema.json|變更紀錄:2026-07-09 專案狀態機修復 B 案 —— 專案建立即in_progress(「未開始(pending)」語意取消),狀態篩選由四鈕改三鈕;批次刪除補not_found計數(FR-047 U-4)
1. 功能描述
專案列表頁是稽核專案的入口與總覽清單:登入後看見自己有份的所有專案,提供搜尋、狀態篩選、排序、分頁,以及建立 / 編輯 / 刪除 / 同步 Drive 的操作入口。清單每一列彙總該專案的框架、最新稽核輪次狀態、輪次數、負責人;點名稱進入該專案的輪次清單頁。
主要使用者:所有登入者(清單只回自己參與的專案);建立 / 編輯 / 刪除受角色與 owner 判定。
角色速覽(完整定義與 manager 全域代行慣例見 _overview §3):
| 角色 | 一句話 |
|---|---|
| owner | 專案負責人(project_extensions.owner_id)— 隱含 manager
等級的編輯 / 刪除權 |
| manager | 專案經理 — 可編輯 / 刪除本專案 |
| auditor / viewer | 只能檢視,功能欄不顯示編輯 / 刪除 |
| admin | 系統管理員(is_admin)—
看得到全部專案、可刪任一專案 |
1.1 功能總覽(本頁全部功能)
| # | 功能 | 說明 | 位置 | 詳述 |
|---|---|---|---|---|
| 1 | 專案清單(分頁 / 排序) | 後端分頁(lazy),每頁 10 / 25 / 50;名稱、稽核輪次數可排序 | 資料表 | UC-PL-01 |
| 2 | 關鍵字搜尋 | 依專案名稱模糊搜尋(Enter 或放大鏡觸發) | 篩選列 | UC-PL-01 |
| 3 | 狀態篩選 | 三鈕:全部 / 進行中(in_progress)/ 已完成(completed)(B 案後專案建立即 in_progress,pending 不再產生,故不提供「未開始」篩選) | 篩選列 | UC-PL-01 |
| 4 | 重設篩選 | 清空搜尋與狀態、還原分頁 | 篩選列 | §10 |
| 5 | 建立專案入口 | 導向專案建立頁(限 CREATE 頁權限) | Header 按鈕 | →專案建立 |
| 6 | 名稱連結進專案 | 點名稱 → 該專案輪次清單頁(project-ap-list) | 名稱欄 | §10 |
| 7 | 框架欄 | 顯示綁定的 module_frame 名稱(FrameworkBadge) | 框架欄 | §6.2 |
| 8 | 最新輪次狀態 | 有輪次顯示「第 N 輪+StatusBadge」;無輪次顯示「尚未啟動稽核」 | 狀態欄 | §6.2 |
| 9 | 編輯專案 | 就地 dialog 改名稱 / 描述(限 owner 或 manager) | 功能欄 → dialog | §6.1 |
| 10 | 刪除專案(單筆) | 軟刪除(限 owner 或 manager / admin) | 功能欄 | UC-PL-02 |
| 11 | 批次刪除 | 勾選多筆一次刪,逐筆檢權部分成功 | Header 按鈕 | UC-PL-02 |
| 12 | 同步專案到 Google Drive | 未同步且 tenant 已連 Drive 時,手動建立專案資料夾 | 功能欄 | UC-PL-03 |
| 13 | Drive 同步徽章 | 名稱旁顯示「已連動 / 未同步 Google Drive」(僅 tenant 已連 Drive 時出現) | 名稱欄 | UC-PL-03 |
UC(§2)只展開 1 / 10 / 11 / 12 這些核心操作路徑;搜尋 / 篩選 / 編輯屬常規列表 CRUD,建立專案另有 專案建立 詳述。
2. Use Case
角色速覽(完整定義與 manager 全域代行慣例見 _overview §3):
| 角色 | 一句話 |
|---|---|
| owner | 專案負責人(project_extensions.owner_id)— 隱含 manager
等級的編輯 / 刪除權 |
| manager | 專案經理 — 可編輯 / 刪除本專案 |
| auditor / viewer | 只能檢視,功能欄不顯示編輯 / 刪除 |
| admin | 系統管理員(is_admin)—
看得到全部專案、可刪任一專案 |
流程圖視覺慣例:菱形 = 判斷、橘底 = 例外 / 擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。
UC-PL-01 瀏覽 / 搜尋 / 篩選專案清單
| 項目 | 內容 |
|---|---|
| 角色 | 任一登入者 |
| 前置條件 | 已登入 |
| 產出 / 後置條件 | 回傳分頁清單(只含自己參與的專案);查詢條件存入 queryStore,重入頁保留 |
UC-PL-02 刪除專案(單筆 / 批次)
| 項目 | 內容 |
|---|---|
| 角色 | owner / manager(admin 可刪任一) |
| 前置條件 | 對該專案為 owner 或 manager;FE 已依此隱藏無權按鈕 |
| 產出 / 後置條件 | 軟刪除:project_extensions.deleted_at 標記 +
projects.status='archived';批次回
{deleted, forbidden, not_found} 計數 |
UC-PL-03 同步專案到 Google Drive
| 項目 | 內容 |
|---|---|
| 角色 | owner / manager |
| 前置條件 | tenant 已連
Drive(driveConnected)且該專案尚未同步(drive_synced=false) |
| 產出 / 後置條件 | 送出 init-folders 佇列任務,由 Drive sync worker
非同步建 5 層資料夾(見 §11) |
3. 權限矩陣
專案層角色基調見 _overview §3。本頁的實際檢查點(FE 判定與 BE 強制分欄並列):
| 操作 | FE 判定 | BE 強制 | BE 檢查位置 |
|---|---|---|---|
| 進入 / 讀清單 | 登入即可 | 只回自己參與的專案(owner 或任一 participant
表命中);is_admin 看全部 |
infra/grc/repository/grc_project_repo_impl.py::list_projects(可見性
join) |
| 建立入口顯示 | pagePermission 含 CREATE |
建立在另頁強制(見 project-create) | — |
| 編輯專案 | UPDATE 權 +(本人為 owner 或該專案 manager) |
需為可見專案(get_project 可見性);participant
全量覆寫時保護 owner 不被移除 |
app/grc/service/project_service.py::update_project |
| 刪除(單筆) | DELETE 權 +(owner 或 manager) |
is_admin 或 owner 或 role=manager,否則
ForbiddenError |
app/grc/service/project_service.py::delete_project:630-644 |
| 批次刪除 | DELETE 權 + 有勾選 |
逐筆同單筆規則,無權者歸 forbidden 不整批失敗 |
同檔 batch_delete_projects:653 |
| 同步 Drive | driveConnected + !drive_synced +(owner
或 manager) |
init-folders endpoint(tenant 須 CONNECTED) | api/cloud_integration/routes/google_drive_sync_route.py |
⚠️ 已知不一致(維護時注意):清單可見性用「owner 或任一 participant 表命中」的寬鬆聯集(含 control / group 層參與),而編輯 / 刪除只認 owner 或專案層 manager——「看得到但不能改」是正常設計,但改權限邏輯時兩處要一起看,見 §12。
4. 狀態機與前置條件
本頁不推進稽核輪次狀態(那在總覽頁 / 規劃頁)。列表僅呈現每專案的狀態:
| 條件 | 效果 | 出處 |
|---|---|---|
| 專案有 ≥1 輪次 | 狀態欄顯示「第 {latest_round_no} 輪」+
latest_round_status 徽章 |
ProjectResponseSchema.latest_round_*(api/grc/serializers/project.py:149-153) |
| 專案無輪次 | 狀態欄顯示「尚未啟動稽核」 | 同上(round_count=0、latest_round_*=None) |
projects.status(粗粒度真相) |
B 案:專案建立即
in_progress;由輪次驅動維護(全輪
closed→completed、否則
in_progress;archived/suspended
為使用者終態不覆寫)。pending 值域於 serializer/enum
保留相容但 BE 不再產生 |
_sync_project_status(app/grc/service/audit_round_app_service.py)+建立點
project_start_app_service.py |
projects.status='archived' |
軟刪除後不再出現於清單(deleted_at IS NULL 過濾) |
grc_project_repo_impl.py::list_projects+delete
軟刪除 |
| 狀態篩選鈕 | FE 只提供 in_progress / completed(B 案後
pending 不再產生故不列);BE _STATUS_VALUES 仍保留
pending/suspended/archived
值域相容,但這三值不開放為篩選鈕 |
FiltersSchema._STATUS_VALUES(api/grc/serializers/project.py:34)+FE
ProjectListView.statusOptions |
5. UI 設計
版面骨架與區塊職責(Header 動作+篩選列+資料表+分頁,就地編輯 dialog 依操作出現;彩色區塊可點跳本頁小節錨點,API 僅標代表性):
實機截圖(STG,亮色模式,2026-07-05,帳號 blsadmin):



狀態呈現:DataTable lazy
後端分頁;多欄排序(sortMode="multiple" +
removableSort);勾選列出現時 Header 才顯示批次刪除;同步
Drive 按鈕帶 per-列 loading(syncingProjectId);Drive
徽章與同步按鈕整組只在 driveConnected
為真時渲染。無 socket,操作後靠重新查詢刷新。
6. API 規格
Envelope:成功
{"status": true, "data": …, "meta": …}(清單帶分頁
meta)、失敗 {"error_code": "…", "msg": "…"}(HTTP
4xx/5xx)。
6.1 總清單(本頁呼叫的全部 endpoint)
| 分類 | Method + Path | 說明 | 完整規格 |
|---|---|---|---|
| 載入 | POST /grc/projects/list |
分頁清單(pager + sort + filters) | §6.2 |
| 載入 | GET /integrations/google-drive |
tenant Drive 連線狀態 → driveConnected |
GAI-SD-02 |
| 編輯 | PUT /grc/project/{uid} |
更新專案(名稱 / 描述 / 日期 / 狀態 / owner / participants) | §6.3 |
| 刪除 | DELETE /grc/project/{uid} |
軟刪除單一專案 | §6.3 |
| 刪除 | POST /grc/projects/batch-delete |
批次軟刪除,逐筆檢權 | §6.3 |
| Drive | POST
/integrations/google-drive/projects/{project_uid}/init-folders |
佇列建立專案 Drive 資料夾 | GAI-SD-02 |
以下 §6.2~6.3 為核心 endpoint 完整規格(欄位逐條對照 serializer /
service 組裝碼,出處標在各段末);標 GAI-SD-02 者屬常規 CRUD,細節見 API
規格書或 Swagger /swagger-ui/。
6.2 載入類
[POST] /grc/projects/list
分頁清單。Request(繼承 RequestMetaSchema 的
pager / sort):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
| pager | object | 否 | {page, page_size, with_total} |
| sort | array | 否 | [{field, order}];可排序欄位僅 name /
start_date / end_date /
status,其餘忽略,預設 projects.id DESC |
| filters.search | string|null | 否 | 專案名稱模糊搜尋(name ILIKE %search%) |
| filters.status | string|array|null | 否 | 單值或陣列(單值 @pre_load 自動轉陣列);允許值
pending/in_progress/completed/suspended/archived |
Response data(陣列,每項為
ProjectResponseSchema =
GrcProjectDto)核心欄位:
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | string | 專案 uid(get_id 回傳 uid,不曝光內部 id) |
| name / description / status | string | 基本資料 |
| completion_rate | float | 整體完成度 % |
| total_tasks / completed_tasks / task_status_breakdown | int / int / object | 任務統計({completed, in_progress, not_started}) |
| control_group_count / total_controls / total_assessment_objects / reviewed_controls_count | int | 控制母體 in-scope 統計(由 profile resolution 推導,失敗回 0 不擋清單) |
| ap_count / round_count | int | 稽核計畫數 / 輪次數 |
| latest_round_no / latest_round_status / latest_round_name | int|null / string|null / string|null | 最新一輪(狀態欄用;無輪次為 null) |
| start_date / end_date | date|null | 專案期程 |
| framework / module_frame | object|null | 框架 / 範本資訊({id, name, …}) |
| owner | object|null | {id, name, avatar_url}(負責人欄) |
| ssp / assessment_plan | object|null | living SSP / AP 摘要 |
| participants | array | 參與者(ProjectParticipantResponse) |
| drive_synced | bool | 是否已有 Drive PROJECT 對應(enrich_drive_synced
批次補;控制徽章與同步鈕) |
出處:serializer
api/grc/serializers/project.py:67-72(request)/
:133-253(response);組裝
app/grc/service/project_service.py::list_projects;可見性與
enrich
infra/grc/repository/grc_project_repo_impl.py::list_projects
6.3 寫入類(常規 CRUD,摘要)
[PUT] /grc/project/{uid}
更新專案。全部欄位選填,只更新有送的(未送的欄位不動)。Request:
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
| name | string | 否 | 名稱變更會回報供 Drive 資料夾改名(§11) |
| description | string | 否 | 專案描述 |
| start_date / end_date | date | 否 | 專案期程 |
| status | string | 否 | 允許值見 _STATUS_VALUES |
| owner_uid | string | 否 | 變更負責人 |
| participants | array | 否 | 送 list 即全量覆寫(含 [] 清空);null
/ 省略 = 不動;owner 受保護不被移除 |
Meta.unknown=EXCLUDE:舊 FE 送audit_systems/devices等未知欄位靜默忽略。
出處:api/grc/serializers/project.py:88-116、app/grc/service/project_service.py::update_project
[DELETE] /grc/project/{uid} ・ [POST] /grc/projects/batch-delete
軟刪除。單筆 body 無;批次 body {uids: [...]},回
{deleted, forbidden, not_found}(部分成功)。權限:is_admin
或 owner 或 role=manager,否則
ForbiddenError(PROJECT_403001);找不到
NotFound(PROJECT_404001)。實作 =
project_extensions.deleted_at=now() +
projects.status='archived'(grc_project_repo_impl.py
軟刪除)。出處:app/grc/service/project_service.py:609-701
7. 前端檔案地圖(compliance-manager-fe/)
| 檔案 | 角色 |
|---|---|
src/views/project/ProjectListView.vue |
主檢視(清單、篩選、分頁、編輯 dialog、刪除、Drive 同步) |
src/config/router/index.js:754-759 |
route
project-list(/project/projects) |
src/composables/useTableQuery.js |
分頁 / 排序 / 篩選共用邏輯 |
src/stores/queryStore.js |
依 scope 保存查詢條件(重入頁還原) |
src/components/grc/StatusBadge.vue |
輪次狀態徽章 |
src/components/grc/FrameworkBadge.vue |
框架名稱徽章 |
src/components/grc/UserAvatar.vue |
負責人頭像 + 名稱 |
src/service/CloudIntegrationService.js |
Drive 連線狀態查詢 / initProjectFolders |
src/config/api/api.js |
endpoint 常數(GRC_PROJECTS_LIST /
GRC_PROJECT / GRC_PROJECTS_BATCH_DELETE
…) |
src/config/locales/i18n/zh-tw/project-list.json |
頁面 i18n |
8. 後端檔案地圖(本頁核心鏈路)
| 鏈路 | Route | App Service | 底層 |
|---|---|---|---|
| 清單 | api/grc/routes/project_route.py::ProjectListResource |
app/grc/service/project_service.py::list_projects(enrich
participants + drive_synced) |
domain/grc/service/grc_project_domain_service.py →
infra/grc/repository/grc_project_repo_impl.py::list_projects(可見性
join + 批次彙總最新輪次 / 控制母體) |
| 更新 | 同檔 ProjectDetailResource.put |
update_project(participant 全量覆寫、owner
保護、改名回報 Drive rename) |
jedi_project domain +
project_participant_service |
| 刪除 | 同檔 ProjectDetailResource.delete |
delete_project(逐筆檢權) |
軟刪除 deleted_at + status='archived' |
| 批次刪除 | 同檔 ProjectBatchDeleteResource.post |
batch_delete_projects(部分成功分類) |
同上 |
DDD 提醒:可見性過濾與最新輪次彙總都在 repo
層批次做(避免 N+1),route 不碰 DB、app service
@transaction——照層級走。
9. DB
9.1 資料表總清單(本頁讀寫的全部表)
| 表 | 讀/寫 | 說明 | 欄位詳述 |
|---|---|---|---|
| compliance.projects | 讀 / 寫(status) | 專案主檔(列表主體);刪除時改 status='archived' |
§9.3 |
| compliance.project_extensions | 讀 / 寫(deleted_at, owner_id) | owner / module_frame / living_ssp 錨點;軟刪除欄位在此 | §9.3 |
| compliance.project_participants | 讀 / 寫(更新時全量覆寫) | 專案層角色(可見性 + 編輯 / 刪除權判定) | GAI-SD-03 |
| compliance.project_audit_rounds | 讀 | 每專案最新一輪 → 狀態欄 / round_count | _overview §2 |
| compliance.control_group_participants / project_group_participants / project_control_participants | 讀 | 可見性聯集(看得到但不一定能改) | GAI-SD-03 |
| compliance.job_executions | 讀 | 任務指派聯集(我有份的專案)+ 完成度統計 | GAI-SD-03 |
| compliance.module_frames | 讀 | 框架 / 範本名稱(框架欄) | GAI-SD-03 |
| oscal.system_security_plans | 讀 | living SSP → 控制母體統計 | GAI-SD-03 |
| public.users | 讀 | 負責人 nickname / avatar | GAI-SD-03 |
| public.drive_folder_mappings | 讀 | 是否已同步 Drive(drive_synced) |
GAI-SD-03 |
9.2 ER 圖(本頁讀寫範圍)
9.3 核心表欄位(取自 DEV DB dump,含 DB comment)
compliance.projects — 專案主檔
| 欄位 | 型別 | 說明 |
|---|---|---|
| uid | uuid | 內部唯一識別碼(API 對外走 uid) |
| name / description | varchar(255) / text | 標題 / 描述 |
| status | varchar(50)|null | 專案狀態(pending / in_progress /
completed / suspended /
archived) |
| start_date / end_date | timestamp|null | 專案期程 |
| tenant_id / org_unit_id | int|null | 租戶 / 組織單位(RLS 隔離) |
compliance.project_extensions — 專案延伸(1:1)
| 欄位 | 型別 | 說明 |
|---|---|---|
| project_id | int | → compliance.projects.id(1:1) |
| owner_id | int|null | 專案負責人(users.id)— 隱含 manager 等級權限 |
| module_frame_id | int|null | 啟動專案用的範本參考(clone 後即獨立,無 FK) |
| living_ssp_id | bigint|null | 專案 living SSP(soft ref)— 控制母體統計的錨點 |
| deleted_at | timestamp|null | 軟刪除時間戳(NULL = 未刪除);清單以此過濾 |
compliance.project_participants — 專案參與者
| 欄位 | 型別 | 說明 |
|---|---|---|
| project_id / user_id | int | 專案 / 使用者 |
| role | varchar(50) | 職責角色:manager / reviewer /
auditor / viewer |
10. 頁面邏輯與資料對應
載入時序:見 §2 UC-PL-01 的載入時序圖(清單查詢與 Drive 狀態並行)。
關鍵欄位對應(API ↔︎ 畫面):
| 畫面元素 | FE state | API 欄位 |
|---|---|---|
| 搜尋框 | filterTable.global.value |
filters.search |
| 狀態篩選鈕 | statusFilter |
filters.status(單值 → BE 轉陣列) |
| 名稱欄連結 | — | data.id(uid)→ router
project-ap-list |
| 框架欄 | — | data.module_frame.name |
| 狀態欄 | — | data.latest_round_no +
data.latest_round_status(無 → 「尚未啟動稽核」) |
| 稽核輪次欄 | — | data.round_count |
| 負責人欄 | — | data.owner.name / owner.avatar_url |
| Drive 徽章 / 同步鈕 | driveConnected |
data.drive_synced |
儲存流程:編輯 dialog 送 PUT → 成功後重查清單;刪除 / 批次刪除確認後呼叫 → 重查。查詢條件寫入 queryStore(scope = 路由名),重入頁還原;重設按鈕清空條件並還原分頁。
錯誤對應:BE error envelope → toast;同步 Drive
常見碼 GRC_404030(tenant 未連 Drive →
提示先到整合設定)、GRC_403001(無權限);刪除
PROJECT_403001(無權)/
PROJECT_404001(不存在)。
11. 背景行為與外部依賴
| 類型 | 內容 |
|---|---|
| Drive 同步 | 「同步到 Drive」→
try_enqueue_init_project_folders;改名 →
try_enqueue_rename_folder(皆 best-effort,失敗只 log
不擋操作);實際建 / 改資料夾由 Drive sync worker 非同步做 |
| 通知 | 更新專案狀態轉 in_progress 時
_notify_project_started(見 update_project) |
| Socket | 本頁無 socket / 輪詢;清單更新靠操作後重新查詢 |
| jedi-* 套件 | jedi-project(專案 domain /
repo)、jedi-auth(JWT / user context / RLS session
變數) |
| 系統參數 | 無 feature flag;Drive 相關 UI 由 tenant 連線狀態動態決定 |
12. 邊界情況與已知坑
- 可見性 vs 編輯權不對齊:清單用寬鬆聯集(owner 或任一 participant 表命中,含控制 / 群組層參與)決定「看得到」,但編輯 / 刪除只認 owner 或專案層 manager。使用者可能「列表看得到卻沒有功能鈕」,屬正常設計,改權限務必兩處一起看
- 軟刪除語意:刪除 =
deleted_at+status='archived',不真正 DELETE;清單以deleted_at IS NULL過濾,archived也不在狀態篩選選項。誤刪需 DB 層還原deleted_at=NULL - 批次刪除部分成功(三類計數已補齊,2026-07-09 FR-047
U-4):無權或不存在的 uid 不會讓整批失敗,分別歸
deleted/forbidden/not_found計數;前端 toast 已呈現三類數量(原僅呈現deleted/forbidden、漏讀not_found,已補齊)。not_found情境=勾選後被他人先刪、隨後fetchProjects()刷新自然消失,困惑度低 - 狀態欄讀的是輪次不是專案
status:列表狀態欄顯示最新輪次狀態(FR-038),與
projects.status(篩選用)是兩套值;別把兩者當同一欄 - 控制母體統計靠 SSP 鏈:
total_controls等由 living SSP → profile → catalog resolution 推導,鏈斷(無 living_ssp_id)時靜默回 0,不擋清單 - Drive
徽章整組條件渲染:
driveConnected=false(tenant 未連 Drive)時,徽章與同步鈕完全不顯示——不是「未同步」狀態,是「沒有 Drive 這回事」 - 排序欄位有限:只有
name/start_date/end_date/status可排序,對「稽核輪次數」欄雖 FE 標field="round_count",BE 排序白名單未含它 → 點了不會照它排(預設id DESC)。修此需在 repo 排序白名單補 - date-only 顯示:
start_date/end_date為 date 型,僅在編輯 dialog / 詳情用;留意 date-only 字串顯示時區陷阱(見專案共用坑)
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 環境
blsadmin(manual 測試用;密碼見.env/ 部署文件) - 導航路徑:登入 → 側邊選單「專案管理 /
專案列表」(
/project/projects) - 前置資料:需至少一個自己有份(owner 或 participant)的專案才看得到列;Drive 相關功能需 tenant 先連 Google Drive
- E2E:
compliance-manager-test/repo(Cucumber + Playwright);以專案列表/project-list搜尋 feature 檔 - 相關文件:建立流程見 專案建立、單專案入口見 專案總覽頁;FR-038(v2
輪次)
docs/features/FR-038-2606-oscal-redesign/;DB / API 全量見 GAI-SD-02/03