Repo:FE | 前置:CM-1077.1(判定端點)+ CM-1077.2(寫入端點) | 母卡:CM-1077
在檢測基準管理頁既有的操作 menu 加兩個項目:修正來源 / 刪除。
母卡 CM-1077 的主張是:「能不能改、能不能刪」由客觀事實決定,不是問使用者想不想。
.1 提供判定端點(回「有沒有被用過、被誰用」),.2 提供寫入端點(修正來源 / 刪除)。本卡只做前端接線與 UX,不碰 BE。
.1 與 .2 都已完成並可打通——本卡沒有它們就無事可做。跟派工者要端點路徑與回應格式,或直接看 BE repo 的 route 定義。CM-1078 已落地(協調者 2026-08-04 實查):
DetectionProfileManageView.vue的 menu 結構已在,buildActionMenuItems(row)用command: () => openEdit(row)這種形式組項目,已含 openDetail/openControls/openEdit/openNewVersion/openFork/openCopyToTool/handleDeactivate。所以本卡直接加 menu item 即可,不需要先重構。
開 menu 前先打 .1 的判定端點。已被使用的版本/主檔,「修正來源」與「刪除」要 disabled,並用 tooltip 說明原因,訊息要帶出使用它的專案名稱:
此版已被 2 個掃描任務使用(Agent 測試專用專案),內容已成為稽核紀錄的一部分,不能再修改。要更換內容請建立新版本。
三個要素缺一不可:被用了幾次 + 被哪些專案用 + 該怎麼辦(建新版本)。只寫「已被使用不可修改」不合格——使用者不知道是誰在用、也不知道下一步。
⚠️ 專案名稱只會列出活著的專案(.1 已處理軟刪穿透)。已軟刪的專案使用者根本看不到,列出來只會造成困惑。若判定端點回的專案清單是空的但使用次數大於 0(全部使用者都在已刪專案裡),tooltip 要能自然降級成不帶專案名的版本,不要印出「()」這種空括號。
母卡原話:兩顆同時擺會讓人不知道該按哪個——條件互斥,讓系統判斷。
| 狀態 | menu 顯示 |
|---|---|
| 從未被使用過 | 刪除 |
| 已被使用過 | 停用 |
也就是不是「兩項都放、刪除灰掉」,而是只出現其中一項。既有的 btn_deactivate 項要改成條件顯示。
(注意這與規則 ① 不衝突:① 講的是「修正來源」這種永遠存在、只是有時不能用的動作;② 講的是刪除/停用這組互斥動作,換的是顯示哪一項。)
主檔刪除是硬刪、連帶清掉底下所有版本與控制項。確認框要列出「將一併刪除 N 個版本、M 條控制項」,不要只寫「確定刪除嗎」。數量從 .1 的判定端點取(若端點沒回這兩個數字,跟 .1 的實作者確認要不要補,不要自己另外多打幾支 API 湊)。
版本層刪除的確認框同理,要說清楚刪的是「哪一版」(帶版號)。
| 層級 | 檔案 | 動作 |
|---|---|---|
| 主檔層(修正/刪除整支基準) | DetectionProfileManageView.vue 的操作 menu |
修正來源、刪除/停用 |
| 版本層(修正某一版來源/刪除某一版) | components/ProfileVersionTable.vue 展開的版本列 |
修正來源、刪除 |
版本列目前只有一顆「控制項」按鈕(emit('open-controls', data.uid),約 L240-252)。要加動作時考慮版本列的空間——多顆按鈕排在一起會擠,可比照主列表收成一顆 menu 觸發鈕。
src/views/detection-profile/
├── DetectionProfileManageView.vue 約 690 行
│ ├── buildActionMenuItems(row) ~L346-425 ← 主要修改點,menu 項目在這裡組
│ ├── rowReadonlyReason(row) ~L331-340 整列級唯讀原因(公版唯讀/已停用)
│ ├── capLabel(key, hasCapability) ~L343 缺 capability 的 label 後綴
│ ├── actionMenuItems / openActionMenu ~L427-437
│ ├── handleDeactivate(row) ~L445-463 ← 二次確認的既有範例,照它的形狀寫刪除
│ └── <ProfileVersionTable> ~L727-730 展開列掛載處
├── DetectionProfileControlsView.vue 控制項詳細頁
├── profileDisplay.js 顯示名解析共用(fmtDateTime / extractionBadge)
└── components/
├── ProfileVersionTable.vue 約 250 行(含模組層 versionCache + invalidateVersionCache)
├── ControlExtractionSummary.vue 抽取摘要(CM-1075 已加「來源過期時的重新解析」按鈕)
├── ProfileFormDialog.vue 建立/編輯對話框(改來源可能可複用其來源輸入區)
├── ProfileNewVersionDialog.vue 版更對話框(改來源的 UI 可直接參考它的形狀)
├── ProfileDetailDialog.vue
└── ProfileCopyDialog.vue
src/service/DetectionToolProfileService.js
既有:listProfiles / listMenu / getProfile / listVersions / createProfile /
updateProfile / createNewVersion / forkProfile / copyToTool /
deactivateProfile / listVersionControls / getExtractionStatus / retryExtraction
← 要加:判定(.1)、修正來源(.2)、刪除(.2)三支
src/config/api/api.js ~L353-379 是 FR-059/060 的 detection profile 端點區
既有形狀:DETECTION_TOOL_PROFILE_DETAIL / _NEW_VERSION / _FORK /
_COPY_TO_TOOL / _DEACTIVATE / _VERSIONS,
DETECTION_PROFILE_VERSION_CONTROLS / _EXTRACTION
← 新端點照同一命名慣例加
src/config/locales/i18n/zh-tw/detection-profile-manage.json
src/config/locales/i18n/en/detection-profile-manage.json
既有 key 形狀:btn_deactivate / dialog_deactivate_title / dialog_deactivate_msg /
toast_deactivate_success / confirm_no / tooltip_inactive_readonly /
tooltip_system_readonly / tooltip_no_permission
confirm.require({ header, message, icon, acceptClass: 'p-button-danger', acceptLabel, rejectLabel, accept }),handleDeactivate 就是現成範例class: 'menu-item-danger'onSaved()(它會重載列表 + 失效下拉快取),不要自己另外拼一套重載invalidateVersionCache(profileUid),否則展開列會拿到過期快取rowReadonlyReason(row)、capability 走 capLabel(...)。本卡新增的「使用中凍結」是第三種原因,與這兩種並存——三者都可能同時成立,disabled 判斷要取聯集,tooltip 訊息要挑最具體的那一個顯示(使用中凍結 > 已停用 > 公版唯讀 > 缺權限)PrimeVue 3 的展開列會整個銷毀重建。
BodyRow 用 <component :is="templates['expansion']"> 渲染展開列,而 templates.expansion 就是父層那個具名插槽的函式本身。父層每次 re-render(展開/收合任一列都會改 expandedRows → 觸發 re-render)都會產生新的插槽函式實例,:is 看到「新的元件型別」就把所有已展開列的內容整個銷毀重建。
後果:展開第二列時第一列的元件實例被換掉、內部狀態歸零 → 看起來像空的;父層存的 ref 也指向已卸載的舊實例。
加 :key 或改具名函式 ref 都擋不住——這是插槽層級的重建。唯一解是讓子元件自己管載入 + 模組層快取(ProfileVersionTable.vue 已經這樣做了:versionCache 在模組層,onMounted 命中快取時同步套用、不閃 spinner)。
與本卡的關係:若在版本列加的動作會觸發父層 re-render(例如刪完一版要重載列表),就會撞到這條——重建後展開列的狀態要能從快取同步還原,不能靠父層 ref 回寫。刪除/修正完成後的正確做法是 invalidateVersionCache(profileUid) 再讓子元件重新載入,而不是直接改子元件的 local state。
(這是 CM-1066 手測時踩到的真實 bug:多列展開時只剩最後一筆有資料。)
CLAUDE.md,以及 BE repo 的 docs/claude/frontend-overview.md(API 慣例 / 設計系統 / 可重用元件 / PrimeVue 3.53 已知 quirks)git checkout <branch> / git switch <branch> 一律不執行,永遠在當下 branch 工作。branch 不對就停下問,不要自己 fixgit add <檔名>,禁用 -amDetectionProfileManageView.vue)components/ProfileVersionTable.vue)