Skip to content

改寫表單 CRUD 流程

問題描述

在改寫列表/表單相關的 CRUD 流程時,表格操作列(檢視、編輯、刪除、匯出等)若散落字串或寬鬆型別,後續擴充或換頁複製時容易不一致。以下整理一套可組合的常數物件型別安全的 handler 對照表做法。

基本表格操作:用 as const 物件取代部分 enum 場景

相較於 enum,常數物件加上 as const 的好處是容易用展開運算子組合、擴充新功能時不必全部擠在同一枚舉裡。缺點是 TypeScript 無法在編譯期證明「所有 value 都不重複」,需靠命名慣例、code review 或測試補強。

ts
export const BaseTableAction = {
  View: "view",
  Edit: "edit",
  Delete: "delete",
  Copy: "copy",
} as const;

組合出特定模組可用的 Action

共用基底再疊加該頁或該模組專用操作,避免重複定義。

ts
export const AdvancedTableAction = {
  ...BaseTableAction,
  Export: "export",
  Archive: "archive",
} as const;

export const FeatureTableAction = {
  ...BaseTableAction,
  Calculate: "calculate",
  Download: "download",
} as const;

單一列表頁:擴充對照表並縮小「實際會實作的操作」聯集

以下為示意:假設某列表列型別為 EntityRow,分頁請求/回應型別由專案 API 層定義(此處以佔位名稱帶過)。

ts
// 沿用前段已定義的 BaseTableAction
/** 本頁在基底操作外再掛載的「語意化」操作鍵(value 請保持唯一) */
export const EntityTableActionMap = {
  ...BaseTableAction,
  RemoveLink: "RemoveLink",
  OpenConfirmModal: "OpenConfirmModal",
} as const;

export type EntityTableActionValue =
  (typeof EntityTableActionMap)[keyof typeof EntityTableActionMap];

/** 僅列出「本表有實作 handler」的操作,避免 Record 必須填滿整張大表 */
export type DataTableImplementedAction =
  | typeof EntityTableActionMap.Delete
  | typeof EntityTableActionMap.RemoveLink
  | typeof EntityTableActionMap.OpenConfirmModal;

export type ActionHandler = (row?: EntityRow) => void | Promise<void>;

/** 對照表,將操作與對應的 handler 進行對應,作為示意,不撰寫實際的handler function */
const actionMap: Record<DataTableImplementedAction, ActionHandler> = {
  [EntityTableActionMap.Delete]: handleDelete,
  [EntityTableActionMap.RemoveLink]: handleRemoveLink,
  [EntityTableActionMap.OpenConfirmModal]: (row) => {
    if (!row) return;
    handleOpenConfirmModal(row);
  },
};

與模板/下拉選單的銜接

在欄位模板中為 slot 參數標上列型別後,handler 可經由 actionMap 以常數為 key 呼叫,減少魔術字串。

vue
<template>
  <el-table-column
    :label="$t('…')"
    prop="action"
    align="center"
    min-width="90">
    <template #default="{ row }: { row: EntityRow }">
      <div>
        <DropdownList
          :shows="['delete']"
          :custom-name="{ delete: $t('…') }"
          @delete="actionMap[EntityTableActionMap.OpenConfirmModal](row)" />
      </div>
    </template>
  </el-table-column>
</template>

實務上會再確認的幾點

  1. 常數 value 是否全域唯一:若與其他模組共用字串,建議前綴或命名空間化,避免事件/權限/log 對應錯對象。(如果有這種需求的話)
  2. DataTableImplementedActionactionMap 要同步:新增操作時記得兩邊一併更新,必要時可用型別讓 Record 強制鍵齊全。
  3. 無法靜態檢查 value 碰撞:若團隊在意,可寫小腳本在 CI 掃描重複 value,或維護單一註冊表產生子集。

以上模式適合「多列表頁共用基底操作、各頁再長出自己的一組語意化動作」的 CRUD/列表流程。