Files
star-erp/.gemini/antigravity/skills/activity-logging/SKILL.md
sky121113 746eeb6f01
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Has been skipped
Koori-ERP-Deploy-System / deploy-production (push) Successful in 49s
更新:優化配方詳情彈窗 UI 與一般修正
2026-01-29 16:13:56 +08:00

5.1 KiB
Raw Blame History

name, description
name description
操作紀錄實作規範 規範系統內 Activity Log 的實作標準,包含後端資料過濾、快照策略、與前端顯示邏輯。

操作紀錄實作規範

本文件說明如何在開發新功能時,依據系統規範實作 spatie/laravel-activitylog 操作紀錄,確保資料儲存效率與前端顯示一致性。

1. 後端實作標準 (Backend)

所有 Model 之操作紀錄應遵循「僅儲存變動資料」與「保留關鍵快照」兩大原則。

1.1 啟用 Activity Log

在 Model 中引用 LogsActivity trait 並實作 getActivitylogOptions 方法。

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):

// ❌ 錯誤:這會導致每次更新都記錄所有欄位,即使它們沒變
activity()
    ->withProperties(['attributes' => $newAttributes, 'old' => $oldAttributes])
    ->log('updated');

正確範例 (Do this):

// ✅ 正確:自行比對差異,只存變動值
$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 (推薦)

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

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

const fieldLabels: Record<string, string> = {
    // ... 既有欄位
    'transaction_date': '費用日期',
    'category': '費用類別',
    'amount': '金額',
};

3. 前端顯示邏輯 (Frontend)

3.1 列表描述生成 (Description Generation)

前端 LogTable.tsx 會依據 properties.snapshot 中的欄位自動組建描述例如「Admin 新增 電話費 公共事業費」)。

若您的 Model 使用了特殊的識別欄位(例如 category必須將其加入 nameParams 陣列中。

位置: resources/js/Components/ActivityLog/LogTable.tsx

const nameParams = [
    'po_number', 'name', 'code', 
    'category_name', 
    'category' // ✅ 確保加入此欄位,前端才能抓到 $snapshot['category']
];

3.2 詳情過濾邏輯

前端 ActivityDetailDialog 已內建智慧過濾邏輯:

  • Created: 顯示初始化欄位。
  • Updated: 僅顯示有變動的欄位 (由 isChanged 判斷)。
  • Deleted: 顯示刪除前的完整資料。

開發者僅需確保傳入的 attributesold 資料結構正確,過濾邏輯會自動運作。

檢核清單

  • Backend: Model 是否已設定 logOnlyDirty 或手動實作過濾?
  • Backend: 是否已透過 tapActivity 或手動方式記錄 Snapshot關鍵名稱
  • Backend: 是否已在 ActivityLogController 加入 Model 中文名稱映射?
  • Frontend: 是否已在 ActivityDetailDialog 加入欄位中文翻譯?
  • Frontend: 若使用特殊識別欄位,是否已加入 LogTablenameParams