狀態:設計確認中|範圍:Frontend only|估時:3-5 天 建立日期:2026-04-25|作者:Raymond + Claude
「資訊系統 (Information System, IS)」與「設備 (Device)」目前在兩個專案設定畫面裡分別佔據獨立的步驟 / Tab。從 user 視角來看,兩者都是「這個專案要稽核的範圍」之組成元件,分散在不同 step / tab 造成認知負擔。
本次需求:將兩者在 UI 層合併到單一區塊「受評範圍」,但保留現有資料模型與後端不動,作為將來 OSCAL SSP 匯出 / 跨模組引用的階段性踏腳石。
| 路由 | 檔案 |
|---|---|
/project/projects/new |
src/views/project/ProjectCreateView.vue |
/project/projects/:projectUID/settings |
src/views/project/ProjectSettingsView.vue |
AuditScopeSection.vue(包裝既有 SystemPicker + DevicePicker)update_project API、PUT /api/1.0/grc-project/<id> request shape、各 service 全部維持information_systems、devices、project_information_systems、project_device_mapping、job_execution_device_mapping、evidence 全部不動SystemPicker.vue / DevicePicker.vue / DeviceManage.vue / InformationSystemManage.vue針對「合併」二字,討論過 5 個層次的方案:
| 方案 | 描述 | 估時 | 風險 |
|---|---|---|---|
| A | 大合併:JSONB 統一 components 表,廢掉 IS/Device | 4-6 週 | 高(影響 job、evidence、跑中流程) |
| B | 統一 component + 多表繼承(基底 + type 子表) | 3-4 週 | 中高 |
| C | UI 合併、資料分開、加 system-device-mapping 多對多 | 2 週 | 中 |
| D | 兩表保留 + 加 device.information_system_id FK + ComponentService 抽象 + 樹狀 ComponentPicker |
1.5-2 週 | 低 |
| E | 純 UI 整併,BE / schema 全不動 | 3-5 天 | 極低 |
關鍵發現(透過外部 reference 掃描):
Device 在 runtime 重度耦合:
job_execution_device_mapping — job 完成判定要 device 全完成evidence.ref_id == device_uid — 證據檔靠 device_uid 分類device_view 複雜聚合 SQL(job × device × evidence × subtask × assignee)IS 相對輕量:只在 project 啟動時做一次性 OSCAL SSP system_characteristic 快照,settings 改 IS 不會影響跑中的 job
存檔流程超簡單:project_service.update_project 對 audit_systems / devices 都是 M2M replace,沒有任何 cascade。UI 怎麼合都不會打壞 BE
OSCAL SSP 匯出是 future:用戶確認 Phase 1 不做匯出。匯出時可選 (a) 純讀現有表 (b) 加 mapping 小表 (c) 加 FK 到 device — 三條路在 Phase 1 都不需 lock-in
E → 任一條 Phase 2 路線的 throwaway 小:picker UI 元件約 3-5 天會被換掉,但 tab 結構、版面、route 都能保留
當未來決定要做 OSCAL SSP 匯出時,可選擇:
information_systems + devices,產出兩個獨立的 OSCAL component / inventory-item 區塊,不改 schemadevice_system_mapping(device_id, system_id),只在匯出時用,不動既有表Phase 1 的設計對以上三條路徑都中立。
ProjectCreateView.vue ProjectSettingsView.vue
│ │
└────────────┬────────────────────┘
│
▼
AuditScopeSection.vue ◄── 新元件(本次新增)
│
┌───────────┴───────────┐
▼ ▼
SystemPicker.vue (既有) DevicePicker.vue (既有)
│ │
▼ ▼
API.INFORMATION_ API.DEVICES /
SYSTEMS_MENU menuStore.deviceMenu
存檔仍走既有 API:
SystemPicker.@change → syncProjectSystems()
→ PUT /api/1.0/grc-project/<id> body: { audit_systems: [...] }
DevicePicker.@change → syncProjectDevices()
→ PUT /api/1.0/grc-project/<id> body: { devices: [...] }
兩支 sync function 維持獨立呼叫(不做合併呼叫)。
| 變更前 | 變更後 |
|---|---|
| Tab 1: 基本資訊 | Tab 1: 基本資訊(不動) |
| Tab 2: 稽核系統 (n) | 🆕 Tab 2: 受評範圍 (n 系統 / m 設備) |
| Tab 3: 參與者 (n) | Tab 3: 參與者(不動) |
| Tab 4: 設備 (n) | (刪除,內容搬到 Tab 2) |
Tab header 計數:分開顯示「3 系統 / 5 設備」格式(不用加總)。
| 變更前 | 變更後 |
|---|---|
| Step 0: 基本資訊 | Step 0: 基本資訊(不動) |
| Step 1: 稽核系統 | (移到 Step 2) |
| Step 2: 參與者 | Step 1: 參與者(位置往前) |
| Step 3: 設備 | (移到 Step 2) |
| Step 4: 確認 | 🆕 Step 2: 受評範圍 → Step 3: 確認 |
確認頁區段:原本 4 區段(基本 / 系統 / 參與者 / 設備)→ 3 區段(基本 / 參與者 / 受評範圍)。受評範圍區段內部展開兩個子區塊:「資訊系統」+「設備」。
AuditScopeSection.vue位置:src/components/grc/AuditScopeSection.vue
版面(上下堆疊,stacked):
┌─ 受評範圍 ───────────────────────────┐
├─────────────────────────────────────┤
│ 📦 資訊系統 [+ 新增] [搜尋...] │
│ • 人事系統 │
│ • 財務系統 │
├─────────────────────────────────────┤
│ 💻 設備 [+ 新增] [搜尋...] │
│ • ap-server-01 │
│ • db-server-01 │
└─────────────────────────────────────┘
Props:
interface Props {
// System
selectedSystemIds: string[]
selectedSystemDetails: Record<string, any>
infoSystemMenu: { id: string; name: string; abbreviation: string | null }[]
// Device
selectedDevices: string[]
selectedDeviceDetails: Record<string, any>
deviceMenu: any[]
// Shared
userMenu: any[]
readonly?: boolean
confirmBeforeRemove?: boolean
}Emits:
defineEmits<{
// System
'update:selectedSystemIds': [string[]]
'update:selectedSystemDetails': [Record<string, any>]
'update:infoSystemMenu': [{...}[]]
'change-systems': []
// Device
'update:selectedDevices': [string[]]
'update:selectedDeviceDetails': [Record<string, any>]
'change-devices': []
'device-created': []
'device-detail-saved': []
}>()內部結構:使用 v-model 雙向綁定既有 SystemPicker / DevicePicker 的所有 props,emit 透傳父層。元件本身不做 API 呼叫、不存狀態,純粹是 layout wrapper。
不顯示:「未來支援層級關聯」hint(user 確認不要)。
後續調整(v0.2):實作後 user 試用發現「合併 tab 沒有 Save 按鈕」與其他有 Save 的 tab 並列產生違和感(沿用原本即時 sync 設計)。後加上:
- Settings 模式下加 ⓘ 小字 hint「本區塊變更會即時自動儲存」(i18n key
section_audit_scope_autosave_hint)syncProjectSystems/syncProjectDevices成功後 toast「已自動儲存」(life 1500ms,i18n keytoast_autosaved)- Create 模式(wizard)不顯示此 hint,因為 wizard 是 deferred save 不是即時 sync
| key | zh-tw | en | 用途 |
|---|---|---|---|
project_settings.tab_audit_scope |
受評範圍 ({systems} 系統 / {devices} 設備) |
Audit Scope ({systems} systems / {devices} devices) |
Settings tab header |
project_settings.section_audit_scope_desc |
本專案要稽核的資訊系統與設備 |
Information systems and devices in scope for this audit |
Settings 模式下區塊描述 |
project_settings.section_audit_scope_autosave_hint |
本區塊變更會即時自動儲存,無需按存檔 |
Changes in this section are saved automatically — no save button needed |
Settings 模式下 ⓘ 小字提示(v0.2 加) |
project_settings.toast_autosaved |
已自動儲存 |
Auto-saved |
sync 成功後 toast(v0.2 加) |
project_create.step_audit_scope |
受評範圍 |
Audit Scope |
Create wizard step header |
project_create.audit_scope_title |
受評範圍 |
Audit Scope |
Create step 內標題 |
project_create.audit_scope_subtitle |
選擇本專案要稽核的系統與設備 |
Select systems and devices for this audit |
Create step 內副標題 |
project_create.review_audit_scope |
受評範圍 |
Audit Scope |
Confirm 頁區段標題 |
project_create.review_audit_scope_systems |
資訊系統 |
Information Systems |
Confirm 頁子區塊 |
project_create.review_audit_scope_devices |
設備 |
Devices |
Confirm 頁子區塊 |
註:
project_settings.section_audit_scope(純標題、不含 desc)原本也有定義但未被消費,留待 cleanup PR 移除。
section_audit_systems / section_devices / tab_audit_systems / tab_devices 仍由內部 SystemPicker / DevicePicker 使用,保留不刪。PR review 時再決定要不要清理舊的 tab key。
依專案既有 i18n 切分慣例(待 implementer 在 implementation 階段確認實際 file 結構):
src/config/locales/i18n/zh-tw/project-settings.json(或對應檔)src/config/locales/i18n/en/project-settings.jsonPUT /api/1.0/grc-project/<id> 已支援以下三種 request body:
// 只更新系統
{ "audit_systems": [{"information_system_uid": "..."}, ...] }
// 只更新設備
{ "devices": [{"device_uid": "..."}, ...] }
// 同時更新(也支援,但 Phase 1 不使用此模式)
{ "audit_systems": [...], "devices": [...] }Phase 1 維持目前 FE 的「即時 sync 各自呼叫」模式:使用者勾選系統 → 立刻 PUT audit_systems;勾選設備 → 立刻 PUT devices。不做合併呼叫。
AuditScopeSection.vue:先獨立元件,定義 props/emits 介面ProjectSettingsView.vue:tab 合併(4 → 3),用 AuditScopeSection 取代原 tab 2 + tab 4ProjectCreateView.vue:step 重排(5 → 4,順序 (2)),確認頁區段更新docs/changelog/2026-04-25-audit-scope-merge.md| 風險 | 機率 | 影響 | mitigation |
|---|---|---|---|
| i18n key rename / 漏翻 | 中 | 低(顯示 raw key) | 兩語系同步加 + grep audit |
| user 找不到原本的 tab | 低 | 低(位置變但功能在) | tab header 計數明確顯示「N 系統 / M 設備」 |
| 確認頁區段重排造成 user 慣性錯亂 | 低 | 極低 | 只是視覺合併,資料不變 |
| AuditScopeSection 透傳 props 漏接 | 中 | 中(功能殘缺) | 開發階段對照 SystemPicker / DevicePicker 完整 props 列表 |
| Step 順序調整造成既有教學文件 / 截圖過時 | 低 | 低 | 改完 PR 描述列出文件需更新清單 |
依 CLAUDE.md 規範,PR 落地時必須建立:
docs/changelog/2026-04-25-audit-scope-merge.md內容包含:需求說明 / 變更檔案清單 / 行為差異(無 API 變更)/ 參考連結到本 design doc。
docs/features/FR-017-2604-audit-scope-merge/implementation-plan.mddocs/changelog/2026-04-25-audit-scope-merge.md(PR 落地時建立)