--- name: 操作紀錄實作規範 description: 規範系統內 Activity Log 的實作標準,包含後端資料過濾、快照策略、與前端顯示邏輯。 --- # 操作紀錄實作規範 本文件說明如何在開發新功能時,依據系統規範實作 `spatie/laravel-activitylog` 操作紀錄,確保資料儲存效率與前端顯示一致性。 ## 1. 後端實作標準 (Backend) 所有 Model 之操作紀錄應遵循「僅儲存變動資料」與「保留關鍵快照」兩大原則。 ### 1.1 啟用 Activity Log 在 Model 中引用 `LogsActivity` trait 並實作 `getActivitylogOptions` 方法。 ```php use Spatie\Activitylog\Traits\LogsActivity; use Spatie\Activitylog\LogOptions; class Product extends Model { use LogsActivity; public function getActivitylogOptions(): LogOptions { return LogOptions::defaults() ->logAll() ->logOnlyDirty() // ✅ 關鍵:只記錄有變動的欄位 ->dontSubmitEmptyLogs(); // 若無變動則不記錄 } } ``` ### 1.2 手動記錄 (Manual Logging) 若需在 Controller 手動記錄(例如需客製化邏輯),**必須**自行實作變動過濾,不可直接儲存所有屬性。 **錯誤範例 (Do NOT do this):** ```php // ❌ 錯誤:這會導致每次更新都記錄所有欄位,即使它們沒變 activity() ->withProperties(['attributes' => $newAttributes, 'old' => $oldAttributes]) ->log('updated'); ``` **正確範例 (Do this):** ```php // ✅ 正確:自行比對差異,只存變動值 $changedAttributes = []; $changedOldAttributes = []; foreach ($newAttributes as $key => $value) { if ($value != ($oldAttributes[$key] ?? null)) { $changedAttributes[$key] = $value; $changedOldAttributes[$key] = $oldAttributes[$key] ?? null; } } if (!empty($changedAttributes)) { activity() ->withProperties(['attributes' => $changedAttributes, 'old' => $changedOldAttributes]) ->log('updated'); } ``` ### 1.3 快照策略 (Snapshot Strategy) 為確保資料被刪除後仍能辨識操作對象,**必須**在 `properties.snapshot` 中儲存關鍵識別資訊(如名稱、代號、類別名稱)。 **主要方式:使用 `tapActivity` (推薦)** ```php public function tapActivity(\Spatie\Activitylog\Contracts\Activity $activity, string $eventName) { $properties = $activity->properties; $snapshot = $properties['snapshot'] ?? []; // 保存關鍵關聯名稱 (避免關聯資料刪除後 ID 失效) $snapshot['category_name'] = $this->category ? $this->category->name : null; $snapshot['po_number'] = $this->code; // 儲存單號 // 保存自身名稱 (Context) $snapshot['name'] = $this->name; $properties['snapshot'] = $snapshot; $activity->properties = $properties; } ``` ## 2. 顯示名稱映射 (UI Mapping) ### 2.1 對象名稱映射 (Mapping) 需在 `ActivityLogController.php` 中設定 Model 與中文名稱的對應,讓前端列表能顯示中文對象(如「公共事業費」而非 `UtilityFee`)。 **位置**: `app/Http/Controllers/Admin/ActivityLogController.php` ```php protected function getSubjectMap() { return [ 'App\Modules\Inventory\Models\Product' => '商品', 'App\Modules\Finance\Models\UtilityFee' => '公共事業費', // ✅ 新增映射 ]; } ``` ### 2.2 欄位名稱中文化 (Field Translation) 需在前端 `ActivityDetailDialog` 中設定欄位名稱的中文翻譯。 **位置**: `resources/js/Components/ActivityLog/ActivityDetailDialog.tsx` ```typescript const fieldLabels: Record = { // ... 既有欄位 'transaction_date': '費用日期', 'category': '費用類別', 'amount': '金額', }; ``` ## 3. 前端顯示邏輯 (Frontend) ### 3.1 列表描述生成 (Description Generation) 前端 `LogTable.tsx` 會依據 `properties.snapshot` 中的欄位自動組建描述(例如:「Admin 新增 電話費 公共事業費」)。 若您的 Model 使用了特殊的識別欄位(例如 `category`),**必須**將其加入 `nameParams` 陣列中。 **位置**: `resources/js/Components/ActivityLog/LogTable.tsx` ```typescript const nameParams = [ 'po_number', 'name', 'code', 'category_name', 'category' // ✅ 確保加入此欄位,前端才能抓到 $snapshot['category'] ]; ``` ### 3.2 詳情過濾邏輯 前端 `ActivityDetailDialog` 已內建智慧過濾邏輯: - **Created**: 顯示初始化欄位。 - **Updated**: **僅顯示有變動的欄位** (由 `isChanged` 判斷)。 - **Deleted**: 顯示刪除前的完整資料。 開發者僅需確保傳入的 `attributes` 與 `old` 資料結構正確,過濾邏輯會自動運作。 ## 檢核清單 - [ ] **Backend**: Model 是否已設定 `logOnlyDirty` 或手動實作過濾? - [ ] **Backend**: 是否已透過 `tapActivity` 或手動方式記錄 Snapshot(關鍵名稱)? - [ ] **Backend**: 是否已在 `ActivityLogController` 加入 Model 中文名稱映射? - [ ] **Frontend**: 是否已在 `ActivityDetailDialog` 加入欄位中文翻譯? - [ ] **Frontend**: 若使用特殊識別欄位,是否已加入 `LogTable` 的 `nameParams`?