選單管理(System Menu Manage)
功能群:系統管理|共用權限模型 / 組織模型 /
system_configs慣例先讀 功能群總覽事實基準:2026-07-05 從 FE / BE code 掃出;BE 邏輯完全來自外部套件
jedi-system-menu(主專案僅 route/serializer/DI wiring),API schema 逐條對照 marshmallow serializer 與套件 service 組裝碼
1. 功能描述
選單管理頁是 public.system_menus 這張下拉 /
分類選單 code table
的維護介面——維護的是像「國家」「產業別」「Agent
類型」「稽核方式」這類業務下拉選項的 group/key/value
種子資料,供全系統其他頁面透過
GET /system/menu/<group>
動態取得下拉選項。這與 RBAC 權限選單樹
public.ui_routes
是完全不同的兩張表、兩套用途——ui_routes
決定登入後左側導覽列有哪些節點可見,system_menus 只是給表單
Dropdown 用的分類代碼表,兩者除了都叫「選單」外沒有關聯(詳見 _overview §4 與 §7 已知坑 1)。
本頁邏輯完全由外部套件
jedi-system-menu(jedi_system_menu
PyPI 套件)提供,主專案只負責 route 掛載、marshmallow serializer 與 DI
container
wiring(di_containers/system_menu/system_menu_containers.py)——domain
entity、repository、ORM model、business rule 皆在套件內,不在主專案
app/system_menu/ 或
domain/system_menu/(該目錄實質是空殼,僅有
__init__.py)。
主要使用者:系統管理員(後台設定層使用者,見 _overview §5 角色基調)。頁面本身只做單層清單 CRUD,無巢狀結構、無版本、無審核流程。
角色速覽:
| 角色 | 一句話 |
|---|---|
| 系統管理員 | 本頁唯一使用者——新增/編輯/刪除選單項;頁面可見性由 RBAC 選單控管(見 §3) |
1.1 功能總覽(本頁全部功能)
| # | 功能 | 說明 | 位置 | 詳述 |
|---|---|---|---|---|
| 1 | 選單項清單(分頁 / 排序 / 搜尋) | 列出全部 system_menus
列,欄位:類別(group)、名稱(key);group 顯示走 i18n
對照(查無翻譯退回原代碼) |
DataTable | §6.1 |
| 2 | 關鍵字搜尋 | 全域搜尋框,比對 group / key
兩欄(allowFilterFields);group 關鍵字若命中
i18n 翻譯文字會被轉回對應原始代碼陣列再送出 |
Header 搜尋框 | §10 |
| 3 | 重置查詢 | 清空搜尋 / 排序 /
分頁回第一頁(queryStore.resetCurrent()) |
Header「重置查詢」 | §10 |
| 4 | 新增選項 | 開 Dialog 填類別(Dropdown,選項來自既有 group 清單)+
名稱;value 由 FE 自動設為與 key 相同 |
Header「新增選項」→ Dialog | UC-SM-01 |
| 5 | 編輯選項 | 開同一顆 Dialog、帶入既有值;儲存走 PUT,同樣
value = key |
逐列「編輯」→ Dialog | UC-SM-01 |
| 6 | 刪除單筆 | 二次確認後 DELETE /system-menu/{id} |
逐列「刪除」 | UC-SM-01 |
| 7 | 批次刪除 | 勾選多列後二次確認;FE 迴圈逐筆打單筆
DELETE(Promise.allSettled),BE
無批次刪除端點;統計成功/失敗數分別 toast |
Header「批次刪除(N)」(選取數 > 0 才顯示) | UC-SM-02 |
| 8 | 類別下拉來源 | Dialog 內「類別」Dropdown 選項=呼叫
GET /system/category-menu 取得的既有 group
清單(不能自由輸入新 group,只能挑既有代碼) |
Dialog | §6.2 / §12 坑 2 |
UC(§2)展開 1(新增/編輯)、7(批次刪除)兩條核心路徑;查詢 / 排序 / 分頁屬常規清單行為,已於 §6.1 / §10 描述完整。本頁沒有「刪除全部下屬項目後才能刪 group」這類巢狀防呆——
system_menus本身無父子結構,group 只是純字串分類欄。
2. Use Case
角色速覽:
| 角色 | 一句話 |
|---|---|
| 系統管理員 | 本頁唯一使用者——新增/編輯/刪除選單項;頁面可見性由 RBAC 選單控管(見 §3) |
流程圖視覺慣例:菱形 = 判斷、橘底 = 例外/擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。
UC-SM-01 新增 / 編輯選單項
| 項目 | 內容 |
|---|---|
| 角色 | 系統管理員 |
| 前置條件 | 已登入;system-menu-manage 選單可見 |
| 產出 / 後置條件 | public.system_menus
新增或更新一列;(group, key) 唯一索引
uq_system_menus_group_key 生效——重複組合寫入未被包裝成
ConflictError,直接以 DB 唯一約束衝突形式冒出(見 §12 坑
1) |
UC-SM-02 批次刪除
| 項目 | 內容 |
|---|---|
| 角色 | 系統管理員 |
| 前置條件 | 已勾選 ≥1 列 |
| 產出 / 後置條件 | 每筆各自呼叫
DELETE /system-menu/{id};Promise.allSettled
統計成功/失敗數,非原子性——部分成功部分失敗是常態結果,不會整批
rollback |
流程圖同上(UC-SM-01 圖下半部涵蓋批次刪除路徑)。
3. 權限矩陣
| 操作 | FE 判定 | BE 強制 | BE 檢查位置 |
|---|---|---|---|
| 選單 / 頁面可見性 | RBAC 選單(ui_routes.name='system-menu-manage',經
2026-05-26-menu-group-restructure.sql 掛在
group-system-admin 底下) |
GET /user/web-menu 依能力集控可見性(見 _overview §2) |
jedi-auth UiRouteDomainService |
| 新增 / 編輯 / 刪除 / 批次刪除 | 無額外前端角色判斷(頁面本身假設只有系統管理員進得來) | ⚠️ 無——全部 7 支 endpoint 僅
@jwt_required(),service
層(jedi_system_menu.app.service.SystemMenuService)不做任何角色
/ 能力檢查 |
api/system_menu/routes/system_menu_route.py(每個 Route
class 逐一) |
| 讀清單 / 分頁查詢 / 取類別 | 同上 | 同上,僅 JWT | 同上 |
⚠️ 已知設計決策(與本群其餘管理頁一致,非本頁獨有遺漏):本頁與 檔案 Agent 管理 等系統管理層頁面同一基調——完全依賴 RBAC 選單可見性擋非管理員使用者看到入口,寫入端點本身不再加角色守門。若使用者已知 API path,任何登入者理論上都能新增/編輯/刪除選單項,詳見 §12 坑 3。
4. 狀態機與前置條件
本頁無輪次 / 階段狀態機(不屬於稽核流程),亦無版本 / 審核概念。僅有的資料層防呆:
| 條件 | 效果 | 出處 |
|---|---|---|
(group, key) 唯一 |
新增 / 更新為重複組合時,DB 唯一索引
uq_system_menus_group_key 擋下寫入 |
scripts/deliverables/out/db_schema.json(indexes);BE
未捕捉此例外轉譯為 4xx(見 §12 坑 1) |
| Dialog 表單必填 | group / key 皆必填(Vuelidate
required),未填擋下送出(FE 專屬,無對應 BE schema
必填強制以外的邏輯) |
SystemMenuManage.vue:36-46 |
value 恆等於 key |
FE 送出前一律
payload.value = payload.key——value 欄位在本頁
UI 無獨立輸入框,實務上是 key 的鏡射值 |
SystemMenuManage.vue:174,189(create /
update) |
清單查詢固定 public=1 |
get_system_manus_and_pager 內部強制
_filter.public = 1——本頁清單只顯示
public=1
的列,public=0(私有)的選單項存在於表但清單看不到、也無
UI 能改回 public=0(新增 Dialog 無此欄位,schema default 為
1) |
jedi_system_menu/app/service/system_menu_service.py:41(get_system_manus_and_pager) |
5. UI 設計
📸 實際畫面截圖待補(一律亮色模式拍攝):以 playwright 對 STG/DEV 擷取,預計補以下兩張到
assets/img/: ① 整頁(搜尋列 + DataTable 清單,含類別/名稱兩欄與逐列編輯/刪除按鈕)sysmenu-overview.png② 新增 / 編輯選項 Dialog(類別 Dropdown + 名稱輸入框)sysmenu-edit-dialog.png
版面骨架(單一 Card:搜尋列 + DataTable,無左樹、無 Tab;新增/編輯共用一顆 Dialog):
狀態呈現:DataTable loading
遮罩(loading.value);新增/儲存按鈕
:loading="submitted";批次刪除按鈕
:loading="batchDeleting";無
socket、無即時推播,操作後一律重打 getMenus()
全量重載清單。分頁 / 排序 / 搜尋狀態經 useTableQuery +
queryStore 依 使用者 uid + route.name 做
per-user scope
持久化(切換使用者或重登會重新套用該使用者上次的查詢狀態)。
6. API 規格
Envelope:成功 {"status": true, "data": …}、失敗
{"status": false, "error_code": "…", "msg": "…"}(common/util/response_util.py:return_response)。
6.1 總清單(本頁呼叫的全部 endpoint)
| 分類 | Method + Path | 說明 | 完整規格 |
|---|---|---|---|
| 載入 | GET /system/category-menu |
取得所有已啟用且公開(enable=1, public=1)的 group
清單,供 Dialog 類別 Dropdown 與搜尋框關鍵字↔︎group 轉換 |
§6.2 |
| 載入 | POST /system/menus |
分頁清單查詢(本頁清單資料來源),固定只回 public=1
的列 |
§6.3 |
| CRUD | POST /system-menu |
新增選單項 | §6.4 |
| CRUD | PUT /system-menu/{id} |
更新選單項 | §6.4 |
| CRUD | DELETE /system-menu/{id} |
刪除選單項(單筆;批次刪除靠 FE 迴圈呼叫此端點) | §6.4 |
| 其他(非本頁呼叫,同 blueprint) | GET /system/menu/{group} |
依 group 取已啟用選單項(enable=1,依 sort
排序)——這是全系統其他頁面消費下拉選項的端點,本頁本身不呼叫 |
§6.5 |
| 其他(同 blueprint,未見任何呼叫方) | POST /system-menu/{id}(SystemMenuRoute
亦掛 POST on <int:id>)、PUT/DELETE
/system-menu(無 id) |
路由存在但語意不明確(POST 帶 id 與 PUT 撞名、PUT/DELETE 缺 id 無法定位單筆)——FE 未呼叫,Swagger 亦未見對應文件化用法,見 §12 坑 4 | — |
標「其他」列出於
routes.json完整比對用;本頁 UI 只實際呼叫 §6.1 前 5 條。
6.2 [GET] /system/category-menu
無 query 參數。取得 enable=1 AND public=1
的全部選單項後,依 group
去重(set()),回傳順序不保證(Python
set 無序)。
Response data:string[](group 代碼陣列,如
["STORAGE_TYPE", "AGENT_TYPE", "ssp_party_role", ...])。
出處:jedi_system_menu/domain/service/system_menu_domain_service.py:57-68(get_menu_categories)、api/system_menu/routes/system_menu_route.py:51-57(SystemGroupCategoryRoute)。
6.3 [POST] /system/menus
Request(RequestMetaSchema +
filters):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
| pager.page / pager.page_size | int | 否 | 預設 1 / 25 |
| pager.with_total | bool | 否 | 預設 true |
| sort[] | array | 否 | {field, order: asc/desc} |
| filters.group | string | 否 | 全域搜尋命中的 group 代碼(陣列或字串,見 §10 轉換邏輯) |
| filters.key | string | 否 | 全域搜尋關鍵字 |
Response data[](每項):
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | int | 主鍵 |
| group | string | 選單群組代碼 |
| key | string | 選單鍵名(顯示名稱) |
| value | string | 選單值(本頁固定 = key) |
| sort | int | 排序順序 |
| enable | int | 1 啟用/0 停用 |
| public | int | 1 公開/0 私有(本查詢固定只回 1) |
⚠️ 服務層固定覆寫
filters.public = 1(_filter.public = 1,system_menu_service.py:41)——即使
FE 未來想查私有選單項,這支 API 也回不了,需另開端點或改此行為才行。
出處:api/system_menu/serializers/system_menu.py:20-21,33-34(SystemMenuPageQueryRequest
/
SystemMenuPageQueryResponse)、jedi_system_menu/app/service/system_menu_service.py:36-51(get_system_manus_and_pager)。
6.4 選單項 CRUD
[POST] /system-menu
Request:
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
| group | string | 是 | 選單群組代碼 |
| key | string | 是 | 選單鍵名 |
| value | string | 是(schema 標 required,FE 固定送 = key) | 選單值 |
| sort | int | 否 | 預設 0(schema default=0;service 層另有
kwargs.get("sort", 1) 的獨立預設 1,兩處預設值不一致,見
§12 坑 5) |
| enable | int | 否 | 預設 1 |
| public | int | 否 | 預設 1 |
Response:同 §6.3 單筆形狀。(group, key) 重複 → DB
唯一索引擋下(未經 try/except 轉譯,見 §12 坑 1)。
出處:api/system_menu/serializers/system_menu.py:4-10(SystemMenuRequest)、jedi_system_menu/app/service/system_menu_service.py:79-98(add_system_menu)。
[PUT] /system-menu/{id}
Request:同上 6 欄位(皆非 partial——service 層
kwargs.get(k, old_value)
逐欄回填舊值,實務上等同全量覆蓋語意)。找不到 id →
404。
Response:同上。出處:jedi_system_menu/app/service/system_menu_service.py:100-121(update_system_menu)。
[DELETE] /system-menu/{id}
無 body。找不到 id →
404。Response:bool(BooleanResponse)。出處:jedi_system_menu/app/service/system_menu_service.py:123-136(delete_system_menu)。
6.5 [GET] /system/menu/{group}(消費端點,非本頁呼叫)
無 query。Response data[]:僅回 enable=1
的列,依 sort
升冪排序((sort or 0));欄位形狀同
§6.3。此端點是全系統其他頁面(如儲存設定的
STORAGE_TYPE、Agent 管理的
AGENT_TYPE)拿下拉選項的實際呼叫點,本頁管理的資料即供應此處。出處:api/system_menu/routes/system_menu_route.py:19-29(SystemGroupMenuRoute)。
7. 前端檔案地圖(compliance-manager-fe/)
| 檔案 | 角色 |
|---|---|
src/views/system-menu-manage/SystemMenuManage.vue |
唯一檢視檔(404 行;無子元件,搜尋列 + DataTable + 新增/編輯 Dialog 全在此) |
src/config/router/index.js:191-198 |
route system-menu-manage,path
/system/system-menu-manage |
src/config/api/api.js:23-26 |
SYSTEM_MENU(/system/menu,消費端點常數,本頁未用)/SYSTEM_MENU_OBJECT(/system-menu,CRUD
用)/SYSTEM_MENUS(/system/menus,分頁查詢)/SYSTEM_MENU_CATEGORY(/system/category-menu) |
src/service/BaseService.js |
本頁未使用專屬 service 檔,直接用 BaseService 泛型
get/post/put/delete |
src/composables/useTableQuery.js |
共用分頁 / 排序 / 搜尋狀態管理(per-user + per-route scope) |
src/stores/queryStore.js |
useTableQuery 依賴的 pinia
store(filterTable / sort / pager
持久化) |
src/config/locales/i18n/{zh-tw,en}/pages.json(system_menu_manage
namespace,約行 240-260) |
頁面文案:新增/編輯/刪除/批次刪除/驗證訊息 |
src/config/locales/i18n/{zh-tw,en}/options.json(menu_category
namespace) |
group 代碼 →
顯示文字對照表(非全量,查無翻譯的 group
直接顯示原代碼,見 §12 坑 6) |
src/config/locales/i18n/{zh-tw,en}/menu.json(system-menu-manage
key) |
側邊選單顯示名稱與說明文字 |
8. 後端檔案地圖(本頁核心鏈路)
| 鏈路 | Route | App Service | 底層 |
|---|---|---|---|
| 選單 CRUD / 清單 / 類別 | api/system_menu/routes/system_menu_route.py(SystemMenuRoute
/ SystemGroupMenusRoute /
SystemGroupCategoryRoute /
SystemGroupMenuRoute) |
jedi_system_menu.app.service.system_menu_service.SystemMenuService(外部套件,@transaction
逐方法裝飾) |
jedi_system_menu.domain.service.system_menu_domain_service.SystemMenuDomainService
→
jedi_system_menu.infra.repository.system_menu_repo_impl.SystemMenuRepoImpl(繼承
BaseRepositoryImpl)→ ORM model
jedi_system_menu.infra.models.system_menu.SystemMenu |
| DI wiring | — | — | di_containers/system_menu/system_menu_containers.py:SystemMenuRepoImpl
為 Singleton、domain/app service 為 Factory——repo 是
Singleton 但因遵循 BaseRepositoryImpl 的 lazy
@property session,不落入 CLAUDE.md 提醒的「Singleton repo
直接 self.session = get_session()」地雷 |
DDD
提醒:本模組幾乎全部業務邏輯落在外部套件,主專案端只有
route(HTTP 邊界)+ serializer + DI 宣告,完全遵守「route 不碰
DB」。但套件內部有一處值得注意:SystemMenuService 每個
method 內部又
system_menu_domain_service = SystemMenuDomainService(SystemMenuRepoImpl())重建一份
domain service / repo,蓋掉建構子注入的
self.system_menu_domain_service(僅
get_system_manus_and_pager
例外,直接用注入實例)——這是套件內部風格不一致,非主專案程式碼,不要在主專案新模組比照這個寫法(正確做法是全程用建構子注入的實例,見
CLAUDE.md DI 段落)。
9. DB
9.1 資料表總清單(本頁讀寫的全部表)
| 表 | 讀/寫 | 說明 | 欄位詳述 |
|---|---|---|---|
| public.system_menus | 寫 | 下拉 / 分類選單 code table(group/key/value +
sort/enable/public),本頁是唯一的維護介面;全系統多處功能(Agent
類型、儲存類型、稽核方式、SSP 角色等)透過
GET /system/menu/<group>
消費此表資料,但寫入只經由本頁 |
§9.3 |
本頁無其他表讀寫。與
ui_routes(RBAC 選單樹)無關聯、無 FK——兩者是同名詞「選單」但完全獨立的表,見 §1 與 _overview §7 坑 1。
9.2 ER 圖(本頁讀寫範圍)
system_menus 為單表、無 FK、無子表,不另畫 ER
圖(單表無關聯繪圖無資訊增量);資料模型定位見 _overview §6
核心資料模型圖(system_menus 已收錄在該圖中,與
ui_routes 併陳對照)。
9.3 核心表欄位(取自 DEV DB dump,含 DB comment)
public.system_menus — 系統菜單表
DB
comment:「系統菜單表:儲存系統菜單項目,以群組和鍵值對方式組織」。無
FK,兩個索引:pk_system_menus(PK on
id)、uq_system_menus_group_key(UNIQUE on
group, key)。
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | integer | 主鍵,nextval('system_menus_id_seq') |
| group | varchar(255) NOT NULL | 菜單群組(如 STORAGE_TYPE / AGENT_TYPE /
ssp_party_role) |
| key | varchar(255) NOT NULL | 菜單鍵名(顯示名稱),與 group 組成唯一索引 |
| value | varchar(255) NOT NULL | 菜單值(本頁 UI 恆等於 key,但 schema 層 value 與 key 是獨立欄位,其他寫入路徑如 SQL migration 種子腳本可能讓兩者不同,如語言代碼 vs 顯示名) |
| sort | integer,nullable,DB
無預設值(default: null) |
排序順序;marshmallow schema 的 default=0 僅影響 dump
方向、非 DB 預設,實際新增經 service 層補 1(見 §12 坑
5) |
| enable | integer,nullable,default 1 |
是否啟用:1 啟用、0 停用 |
| public | integer,nullable,default 1 |
是否公開:1 公開、0 私有;本頁清單查詢固定只回
public=1 |
10. 頁面邏輯與資料對應
載入時序:onMounted
序列(非平行)執行:先
getMenuCategories()(GET /system/category-menu)取得
group 代碼清單存入 categories(供 Dropdown 選項與 i18n
反查),完成後才
getMenus()(POST /system/menus)載入分頁清單——getMenus
內部依賴 categories 做「關鍵字→group
代碼」轉換,因此刻意不平行化(程式碼註解已明講原因)。
關鍵欄位對應(API ↔︎ 畫面):
| 畫面元素 | FE state | API 欄位 |
|---|---|---|
| 類別欄顯示文字 | transDesc(data.group) |
data.group(查
lang.menu_category.<code> i18n
key,查無退回原代碼) |
| 名稱欄 | 直接綁定,超長截斷+tooltip | data.key |
| 搜尋框 | filterTable.global.value |
filters.group /
filters.key(兩欄同時比對關鍵字) |
| 類別 Dropdown 選項 | categories({key: 翻譯文字, value: 原代碼}[]) |
GET /system/category-menu 回傳的 group 代碼陣列 |
| Dialog 名稱輸入框 | currentOption.key |
送出時 payload.value = payload.key(FE 鏡射,非 API
強制) |
搜尋關鍵字 → group
代碼轉換邏輯(getMenus()
內,SystemMenuManage.vue:111-127):若使用者在全域搜尋框輸入的字串命中
i18n 翻譯後的文字(而非原始代碼),FE 會反查 categories
找出所有翻譯文字包含該關鍵字的原代碼,組成陣列覆蓋
payload.filters.group;若找不到任何對應代碼,直接移除
filters.group(避免把翻譯後的中文字串送給 BE
比對原始英文代碼、必然查無結果)。
儲存流程:新增 / 編輯共用 onSaveClick()
→ Vuelidate 驗證 → 依 currentOption.id 是否存在分流
create() / update();兩者皆先
payload.value = payload.key 再送出;成功後關閉 Dialog +
重打 getMenus() 全量重載(無 local state 增量更新)。
錯誤對應:BE error envelope → toast 顯示;已知碼
SYSTEM_MENU_404001(SYSTEM_MENU_DATA_NOT_FOUND,編輯/刪除不存在的
id);(group,key) 重複寫入未特別轉譯 error code,直接是 DB
例外冒出的通用 500(見 §12 坑 1)。
11. 背景行為與外部依賴
| 類型 | 內容 |
|---|---|
| 通知 | 無——本頁不觸發、也不消費任何通知 |
| Socket | 無 socket / 輪詢;操作後皆重打 getMenus()
手動重新整理 |
| jedi-* 套件 | jedi-system-menu(本頁全部
domain/repo/ORM 邏輯所在,主專案僅 route + serializer + DI
wiring)、jedi-common(@transaction /
BaseRepositoryImpl / RequestMetaSchema /
EnvelopeSchema)、jedi-auth(JWT) |
| 系統參數 | 無 feature flag;system_menus
本身即是「系統參數表」的一種——其他模組的下拉選項(STORAGE_TYPE/AGENT_TYPE/AUDIT_METHOD/ssp_party_role
等)皆以此表為資料源,種子值由對應 FR 的 SQL migration 腳本插入(如
scripts/sql/2026-06-19-fr039-agent-type-menu.sql) |
12. 邊界情況與已知坑
(group, key)唯一約束衝突未轉譯成標準錯誤格式:新增 / 更新為既有(group, key)組合時,SystemMenuRepoImpl.add_menu()/update_menu()均無 try/except 包裝 SQLAlchemyIntegrityError,直接讓例外冒出——最終使用者看到的是未分類的 500,而非語意明確的 409ConflictError。新增類似 CRUD 頁時勿比照此寫法(應照 CLAUDE.md「Exception 類別對照」用ConflictError+ 對應 error code)- 類別 Dropdown 只能挑既有
group,無法新增全新分類:新增 Dialog 的類別欄位是純
Dropdown(選項來自
GET /system/category-menu),沒有「輸入新分類」的自由文字欄——要新增一個全新 group(如未來的NEW_FEATURE_TYPE),必須先有至少一筆該 group 的種子資料存在(通常靠 SQL migration 先插入第一筆),UI 本身無法憑空建立新 group。這是設計如此,非 bug - 寫入 API 全數無角色檢查:與本群其餘管理頁一致(見
_overview §5),本頁 7 支 endpoint 僅
@jwt_required(),service 層不做角色 / 能力守門,完全依賴 RBAC 選單可見性擋非管理員使用者看到入口。改動時勿誤以為是可隨意加嚴或放寬的細節,這是既有系統性設計,不是本次盤點新發現的缺口 /system-menu/{id}有 POST、/system-menu(無 id)有 PUT/DELETE,但無任何呼叫方:routes.json列出POST /api/1.0/system-menu/<int:id>、PUT /api/1.0/system-menu、DELETE /api/1.0/system-menu(皆綁同一個SystemMenuRouteclass),但該 class 只定義了post(無 id 參數)、put(self, id, ...)、delete(self, id, ...)三個方法——Flask-RESTful 對同一 URL 規則若方法簽名與路由參數不匹配會在呼叫時报錯而非啟動時。FE 完全沒有呼叫這些形狀(FE 固定用「POST 無 id 建立」「PUT/DELETE 帶 id 操作單筆」),Swagger 文件也被註解關閉(# docs.register(...)),這些多餘路由組合疑似歷史殘留或路由宣告誤植,新人不要誤以為是可用的替代呼叫方式sort預設值兩處不一致:marshmallow schemaSystemMenuRequest.sort的default=0(僅影響 dump 方向,非 load 驗證),但jedi_system_menuservice 層add_system_menu()對缺sort的 kwargs 補的是1(if "sort" not in kwargs: kwargs["sort"] = 1)——若未來要依賴「不填 sort 就是 0」的假設寫下游邏輯,需注意實際落地值是 1 不是 0menu_categoryi18n 對照表非全量:options.json的menu_categorynamespace 只收錄少數幾個 group 的中文翻譯(如ENABLE_STATUS/ANS_TYPE/SYSTEM_TYPE等),本頁清單常見的STORAGE_TYPE/AGENT_TYPE/ssp_party_role等 group 未列在此翻譯表,transDesc()查無對應 key 時會直接顯示原始英文/底線命名代碼——清單上看到未翻譯的原始代碼字串是預期行為,不是漏翻譯 bug(除非該 group 本來就打算給使用者可讀的中文名)- 本頁清單固定看不到
public=0的私有選單項:get_system_manus_and_pager()內部強制覆寫_filter.public = 1,且新增 Dialog 無public輸入欄位(schema default 為 1)——若資料庫中存在public=0的舊資料(理論上可能由早期 SQL migration 種子腳本插入),本頁完全看不到也管不到,需直接查 DB 或改本頁邏輯才能觸及 ui_routesvssystem_menus別混(引用 _overview §7 坑 1):本頁維護的是下拉分類 code table,跟 RBAC 選單樹是兩回事——新人最容易搞混的地方,別在改權限選單邏輯時誤動到這張表,反之亦然
13. 開發與驗證
- 跑起來:BE
python main_socketio.py(port 8000,log 在log/app.log);FE 在compliance-manager-fe/起 Vite dev server。BE 改 service code 後必須重啟(無 hot reload)——但本頁邏輯在外部套件jedi-system-menu,改動需走 CLAUDE.md「外部套件異動規範」(dev 期走 poetry path dependency) - 測試帳號:dev 環境系統管理員角色帳號(密碼見
.env/ 部署文件) - 導航路徑:登入 →
系統設定選單「選單管理」(
system-menu-manage,path/system/system-menu-manage) - 前置資料:無強制前置資料即可開頁(空清單可正常顯示);要驗證「類別
Dropdown 有選項」需 DB 內已存在至少一筆
system_menus資料(enable=1, public=1),全新環境若尚未跑過任何 seed migration,Dropdown 會是空的 - E2E:
compliance-manager-test/repo(Cucumber + Playwright);本頁相關 feature 檔以system-menu/選單管理搜尋 - 相關文件:
docs/交付文件/v1.8.0/src/doc-02-api-spec/system_menu.md(GAI-SD-02 API 規格書對應段);DB / API 全量見 GAI-SD-02/03;jedi-system-menu套件源碼~/Projects/Jedicogy/module/jedi-python-package/jedi-system-menu/(若本機有 clone;本次事實蒐集實際讀取的是.venv/lib/python3.11/site-packages/jedi_system_menu/已安裝版本)