skills/rytass/utils/cms-react

cms-react

SKILL.md

CMS React Components (CMS React 元件)

Overview

@rytass/cms-react-components 提供基於 Mezzanine UI 的 CMS 管理介面元件,支援文章生命週期管理、權限控制和審核流程。

Quick Start

安裝

npm install @rytass/cms-react-components @mezzanine-ui/react @mezzanine-ui/core

Provider 設定

import { DialogProvider, ModalProvider } from '@rytass/cms-react-components';

function App() {
  return (
    <DialogProvider>
      <ModalProvider>
        <YourCMSApp />
      </ModalProvider>
    </DialogProvider>
  );
}

Core Components

StandardCMSTable

進階資料表格,支援排序、過濾、分頁、批次操作:

import {
  StandardCMSTable,
  ArticleStage,
  ArticlesPermissions,
  ArticleTableActions,
} from '@rytass/cms-react-components';

const ArticleList = () => {
  const columns = [
    { title: '標題', dataIndex: 'title' },
    { title: '建立時間', dataIndex: 'createdAt' },
    { title: '狀態', dataIndex: 'stage' },
  ];

  return (
    <StandardCMSTable<Article>
      columns={columns}
      dataSource={articles}
      currentStage={ArticleStage.DRAFT}
      userPermissions={[
        ArticlesPermissions.CreateArticle,
        ArticlesPermissions.UpdateArticleInDraft,
        ArticlesPermissions.DeleteArticleInDraft,
        ArticlesPermissions.SubmitPutBackArticle,
      ]}
      // 可選:自訂各階段可用操作
      actions={{
        [ArticleStage.DRAFT]: [
          ArticleTableActions.Update,
          ArticleTableActions.Submit,
          ArticleTableActions.Delete,
        ],
      }}
      actionsEvents={{
        onView: async (article) => router.push(`/articles/${article.id}`),
        onSubmit: async (article) => await submitArticle(article.id),
        onDelete: async (article) => await deleteArticle(article.id),
        onPutBack: async (article) => await putBackArticle(article.id),
        onRelease: async (article, releasedAt) => await releaseArticle(article.id, releasedAt),
        onWithdraw: async (article) => await withdrawArticle(article.id),
        onApprove: async (article) => await approveArticle(article.id),
        onReject: async (article, reason) => await rejectArticle(article.id, reason),
      }}
    />
  );
};

StandardCMSFormActions

表單操作按鈕,根據權限和階段顯示:

import {
  StandardCMSFormActions,
  ArticleStage,
  ArticlesPermissions,
} from '@rytass/cms-react-components';
import { useForm } from 'react-hook-form';

interface ArticleFormData {
  title: string;
  content: string;
}

const ArticleForm = () => {
  const methods = useForm<ArticleFormData>();

  return (
    <StandardCMSFormActions<ArticleFormData>
      methods={methods}                          // react-hook-form 的 UseFormReturn
      currentStage={ArticleStage.DRAFT}          // 當前文章階段
      userPermissions={[                         // 使用者權限
        ArticlesPermissions.CreateArticle,
        ArticlesPermissions.UpdateArticleInDraft,
        ArticlesPermissions.SubmitPutBackArticle,
      ]}
      createMode={true}                          // 建立模式 (vs 編輯模式)
      actionsEvents={{
        // 建立模式下的操作
        onCreateToDraft: async (data) => await saveToDraft(data),
        onCreateAndSubmit: async (data) => await createAndSubmit(data),
        onCreateAndRelease: async (data, releasedAt) => {
          await createAndRelease(data, releasedAt);
        },
        onCreateAndApprove: async (data) => await createAndApprove(data),

        // 編輯模式下的操作
        onUpdateToDraft: async (data) => await updateToDraft(data),
        onUpdateAndSubmit: async (data) => await updateAndSubmit(data),
        onUpdateAndRelease: async (data, releasedAt) => {
          await updateAndRelease(data, releasedAt);
        },
        onUpdateAndApprove: async (data) => await updateAndApprove(data),

        // 通用操作
        onSubmit: async (data) => await submitForReview(data),
        onRelease: async (data, releasedAt) => await release(data, releasedAt),
        onApprove: async (data) => await approve(data),
        onReject: async (data, reason) => await reject(data, reason),
        onLeave: async (data) => router.back(),
        onGoToEdit: async (data) => router.push(`/articles/${data.id}/edit`),
      }}
      leaveButtonText="返回列表"                  // 可選:自訂離開按鈕文字
      actionButtonText="儲存草稿"                 // 可選:自訂操作按鈕文字
      submitButtonText="送審"                     // 可選:自訂送出按鈕文字
      disableLeaveButton={(values) => methods.formState.isSubmitting}
      disableActionButton={(values) => !values.title}
      disableSubmitButton={(values) => !values.title || !values.content}
    >
      {/* 表單欄位 */}
      <input {...methods.register('title')} placeholder="標題" />
      <textarea {...methods.register('content')} placeholder="內容" />
    </StandardCMSFormActions>
  );
};

StandardCMSFormActionsProps:

屬性 型別 必填 說明
methods UseFormReturn<T> react-hook-form 的表單方法
currentStage ArticleStage 當前文章階段
userPermissions ArticlesPermissions[] 使用者權限
actionsEvents StandardCMSFormActionsEventsProps<T> 各操作回調函式
createMode boolean 是否為建立模式(影響顯示的按鈕)
children ReactNode 表單內容
className string 容器 className
actionsClassName string 按鈕區 className
leaveButtonText string 離開按鈕文字
actionButtonText string 操作按鈕文字
submitButtonText string 送出按鈕文字
disableLeaveButton (values: T) => boolean 離開按鈕禁用條件
disableActionButton (values: T) => boolean 操作按鈕禁用條件
disableSubmitButton (values: T) => boolean 送出按鈕禁用條件
onLeave (values: T) => Promise<void> 離開按鈕事件(優先於 actionsEvents.onLeave)
onAction (values: T) => Promise<void> 操作按鈕事件
onSubmit (values: T) => Promise<void> 送出按鈕事件(優先於 actionsEvents.onSubmit)

StandardCMSList

整合 Tabs + Table 的完整文章列表元件:

import {
  StandardCMSList,
  ArticleStage,
  ArticlesPermissions,
} from '@rytass/cms-react-components';

const ArticleManagement = () => {
  return (
    <StandardCMSList<Article>
      columns={columns}
      dataSource={articles}
      defaultStage={ArticleStage.DRAFT}
      userPermissions={[
        ArticlesPermissions.CreateArticle,
        ArticlesPermissions.UpdateArticleInDraft,
        ArticlesPermissions.DeleteArticleInDraft,
      ]}
      onTabChange={(stage) => {
        // 切換 Tab 時重新載入資料
        fetchArticles(stage);
      }}
      tabsNaming={{
        [ArticleStage.DRAFT]: '草稿區',
        [ArticleStage.REVIEWING]: '待審核',
        [ArticleStage.VERIFIED]: '可發佈',
        [ArticleStage.SCHEDULED]: '已預約',
        [ArticleStage.RELEASED]: '已發佈',
      }}
      actionsEvents={{
        onView: (article) => router.push(`/articles/${article.id}`),
        onSubmit: async (article) => await submitArticle(article.id),
        onDelete: async (article) => await deleteArticle(article.id),
      }}
    />
  );
};

StandardCMSListProps:

繼承 StandardCMSTableProps(除了 currentStage),額外提供:

屬性 型別 必填 說明
defaultStage ArticleStage 預設 Tab(預設 DRAFT
onTabChange (stage: ArticleStage) => void Tab 切換回調
tabsNaming { [key in ArticleStage]?: string } 自訂 Tab 名稱
tableClassName string Table 自訂 className

StandardCMSTabs

獨立使用的文章狀態 Tabs:

import { StandardCMSTabs, ArticleStage } from '@rytass/cms-react-components';

const [activeStage, setActiveStage] = useState(ArticleStage.DRAFT);

<StandardCMSTabs
  activeStage={activeStage}
  onChange={(stage) => setActiveStage(stage)}
  tabsNaming={{
    [ArticleStage.RELEASED]: '已發佈',
    [ArticleStage.SCHEDULED]: '已預約',
    [ArticleStage.VERIFIED]: '可發佈',
    [ArticleStage.REVIEWING]: '待審核',
    [ArticleStage.DRAFT]: '草稿區',
  }}
/>

StandardCMSTabsProps:

屬性 型別 必填 說明
activeStage ArticleStage 當前選中的階段
onChange (stage: ArticleStage) => void Tab 切換回調
tabsNaming { [key in ArticleStage]?: string } 自訂 Tab 名稱

預設 Tab 順序: 已發佈 → 已預約 → 可發佈 → 待審核 → 草稿區

Modals

DeleteWithdrawModal

刪除/撤下選擇對話框(讓使用者選擇要刪除還是撤下):

import {
  useModal,
  DeleteWithdrawModal,
  DeleteWithdrawModalRadio,
} from '@rytass/cms-react-components';

const { openModal } = useModal();

openModal({
  children: (
    <DeleteWithdrawModal
      showSeverityIcon={false}
      defaultRadioValue={DeleteWithdrawModalRadio.Withdraw}
      withDelete={true}
      withWithdraw={true}
      onDelete={async () => {
        await deleteArticle(id);
        // closeModal() 由元件內部自動呼叫
      }}
      onWithdraw={async () => {
        await withdrawArticle(id);
        // closeModal() 由元件內部自動呼叫
      }}
    />
  ),
});

DeleteWithdrawModalProps:

屬性 型別 必填 說明
showSeverityIcon boolean 顯示警告圖示(預設 false
defaultRadioValue DeleteWithdrawModalRadio 預設選項
withDelete boolean 顯示「永久刪除」選項
withWithdraw boolean 顯示「移至可發佈區」選項
onDelete () => Promise<void> 刪除回調
onWithdraw () => Promise<void> 撤下回調

DeleteWithdrawModalRadio:

enum DeleteWithdrawModalRadio {
  Delete = 'Delete',     // 永久刪除
  Withdraw = 'Withdraw', // 撤下至可發佈區
}

RejectModal

審核拒絕對話框(含理由輸入):

import { useModal, RejectModal } from '@rytass/cms-react-components';

const { openModal } = useModal();

openModal({
  children: (
    <RejectModal
      onReject={async (reason) => {
        await rejectArticle(id, reason);
        // closeModal() 由元件內部自動呼叫
      }}
    />
  ),
});

RejectModalProps:

屬性 型別 必填 說明
onReject (reason: string) => Promise<void> 拒絕回調,帶入使用者輸入的不通過原因

注意: RejectModal 會自動顯示「審核不通過」標題和說明文字,使用者必須輸入不通過原因才能送出。Modal 會在 onReject 完成後自動關閉。

VerifyReleaseModal

發佈/審核多功能對話框:

import {
  useModal,
  VerifyReleaseModal,
  VerifyReleaseModalRadio,
} from '@rytass/cms-react-components';

openModal({
  children: (
    <VerifyReleaseModal
      title="發佈文章"
      showSeverityIcon={false}
      defaultRadioValue={VerifyReleaseModalRadio.Now}
      withApprove={true}
      withReject={true}
      onRelease={async (releasedAt: string) => {
        await releaseArticle(id, releasedAt);
      }}
      onApprove={async () => {
        await approveArticle(id);
      }}
      onReject={async (reason: string) => {
        await rejectArticle(id, reason);
      }}
    />
  ),
});

VerifyReleaseModalProps:

屬性 型別 必填 說明
title string Modal 標題
showSeverityIcon boolean 顯示警告圖示
defaultRadioValue VerifyReleaseModalRadio 預設選項(預設 Now
withApprove boolean 顯示「即刻通過」選項
withReject boolean 顯示「不通過」選項
onRelease (releasedAt: string) => Promise<void> 發佈回調(ISO 格式時間)
onApprove () => Promise<void> 通過審核回調
onReject (reason: string) => Promise<void> 拒絕審核回調

LogsModal

版本歷史與稽核日誌:

import { useModal, LogsModal, LogsStageData, ArticleStage } from '@rytass/cms-react-components';

const { openModal } = useModal();

// 定義取得日誌資料的函式
const fetchLogsData = async (): Promise<LogsStageData> => {
  // 從 API 或其他來源取得文章各階段的日誌資料
  return {
    [ArticleStage.DRAFT]: {
      createdAt: '2024-01-01T00:00:00Z',
      createdBy: '王小明',
      updatedAt: '2024-01-02T10:00:00Z',
      updatedBy: '王小明',
      submittedAt: '',
      submittedBy: '',
      verifiedAt: '',
      verifiedBy: '',
      releasedAt: '',
      releasedBy: '',
      version: 1,
    },
    [ArticleStage.REVIEWING]: {
      createdAt: '',
      createdBy: '',
      updatedAt: '',
      updatedBy: '',
      submittedAt: '2024-01-03T09:00:00Z',
      submittedBy: '王小明',
      verifiedAt: '',
      verifiedBy: '',
      releasedAt: '',
      releasedBy: '',
      version: 2,
    },
    // ... 其他階段
  };
};

openModal({
  children: (
    <LogsModal
      onGetData={fetchLogsData}
      stageWording={{
        [ArticleStage.DRAFT]: {
          stageName: '草稿',
          timeTitle: '最後編輯時間',
          memberTitle: '編輯人員',
        },
        [ArticleStage.REVIEWING]: {
          stageName: '待審核',
          timeTitle: '送審時間',
          memberTitle: '送審人員',
        },
        // ... 可自訂各階段的文字
      }}
    />
  ),
});

LogsModalProps

屬性 型別 必填 說明
onGetData () => Promise<LogsStageData> 取得日誌資料的非同步函式
stageWording { [keys in ArticleStage]?: { stageName?, timeTitle?, memberTitle? } } 自訂各階段的顯示文字

LogsStageData 與 LogsData

// 各階段的日誌資料
type LogsStageData = {
  [keys in ArticleStage]?: LogsData | null;
};

// 單一階段的詳細資料
interface LogsData {
  createdAt: string;
  createdBy: string;
  updatedAt: string;
  updatedBy: string;
  submittedAt: string;
  submittedBy: string;
  verifiedAt: string;
  verifiedBy: string;
  releasedAt: string;
  releasedBy: string;
  version?: number;   // 版本號
  reason?: string;    // 退回原因(僅 DRAFT 階段顯示)
}

TypeScript Interfaces

Events Props

// Table 操作事件
interface StandardCMSTableEventsProps<T extends TableDataSourceWithID> {
  onView?: (source: T) => Promise<void>;
  onSubmit?: (source: T) => Promise<void>;
  onPutBack?: (source: T) => Promise<void>;
  onRelease?: (source: T, releasedAt: string) => Promise<void>;
  onWithdraw?: (source: T) => Promise<void>;
  onApprove?: (source: T) => Promise<void>;
  onReject?: (source: T, reason: string) => Promise<void>;
  onDelete?: (source: T) => Promise<void>;
}

// FormActions 操作事件
interface StandardCMSFormActionsEventsProps<T extends FieldValues> {
  onLeave?: (values: T) => Promise<void>;
  onGoToEdit?: (values: T) => Promise<void>;
  onCreateToDraft?: (values: T) => Promise<void>;
  onCreateAndRelease?: (values: T, releasedAt: string) => Promise<void>;
  onCreateAndApprove?: (values: T) => Promise<void>;
  onCreateAndSubmit?: (values: T) => Promise<void>;
  onUpdateToDraft?: (values: T) => Promise<void>;
  onUpdateAndRelease?: (values: T, releasedAt: string) => Promise<void>;
  onUpdateAndApprove?: (values: T) => Promise<void>;
  onUpdateAndSubmit?: (values: T) => Promise<void>;
  onRelease?: (values: T, releasedAt: string) => Promise<void>;
  onApprove?: (values: T) => Promise<void>;
  onReject?: (values: T, reason: string) => Promise<void>;
  onSubmit?: (values: T) => Promise<void>;
}

ArticleTableActionsType

定義各階段可用的 Table 操作:

interface ArticleTableActionsType {
  [ArticleStage.DRAFT]?: (
    | ArticleTableActions.Update
    | ArticleTableActions.Submit
    | ArticleTableActions.Release
    | ArticleTableActions.Delete
  )[];
  [ArticleStage.REVIEWING]?: (
    | ArticleTableActions.Update
    | ArticleTableActions.Review
    | ArticleTableActions.Delete
    | ArticleTableActions.PutBack
  )[];
  [ArticleStage.VERIFIED]?: (
    | ArticleTableActions.View
    | ArticleTableActions.Update
    | ArticleTableActions.Release
    | ArticleTableActions.Delete
  )[];
  [ArticleStage.SCHEDULED]?: (
    | ArticleTableActions.View
    | ArticleTableActions.Update
    | ArticleTableActions.Withdraw
  )[];
  [ArticleStage.RELEASED]?: (
    | ArticleTableActions.Update
    | ArticleTableActions.Delete
  )[];
  [ArticleStage.UNKNOWN]?: [];
}

Enums

ArticleStage

enum ArticleStage {
  DRAFT = 'DRAFT',           // 草稿
  REVIEWING = 'REVIEWING',   // 審核中
  VERIFIED = 'VERIFIED',     // 已核准
  SCHEDULED = 'SCHEDULED',   // 預約發布
  RELEASED = 'RELEASED',     // 已發布
  UNKNOWN = 'UNKNOWN',       // 未知狀態
}

ArticlesPermissions

細顆粒度權限(按文章狀態細分):

enum ArticlesPermissions {
  // 通用權限
  CreateArticle = 'CreateArticle',               // 建立文章
  SubmitPutBackArticle = 'SubmitPutBackArticle', // 送審/退回
  ApproveRejectArticle = 'ApproveRejectArticle', // 審核通過/拒絕

  // Draft 草稿階段
  UpdateArticleInDraft = 'UpdateArticleInDraft',
  DeleteArticleInDraft = 'DeleteArticleInDraft',

  // Reviewing 審核中階段
  UpdateArticleInReviewing = 'UpdateArticleInReviewing',
  DeleteArticleInReviewing = 'DeleteArticleInReviewing',

  // Verified 已核准階段
  UpdateArticleInVerified = 'UpdateArticleInVerified',
  ReleaseArticleInVerified = 'ReleaseArticleInVerified',
  DeleteArticleInVerified = 'DeleteArticleInVerified',

  // Scheduled 預約發布階段
  UpdateArticleInScheduled = 'UpdateArticleInScheduled',
  ReleaseArticleInScheduled = 'ReleaseArticleInScheduled',
  WithdrawArticleInScheduled = 'WithdrawArticleInScheduled',

  // Released 已發布階段
  UpdateArticleInReleased = 'UpdateArticleInReleased',
  ReleaseArticleInReleased = 'ReleaseArticleInReleased',
  WithdrawArticleInReleased = 'WithdrawArticleInReleased',
  DeleteArticleInReleased = 'DeleteArticleInReleased',
}

ArticleTableActions

注意:使用 PascalCase(非 SCREAMING_SNAKE_CASE)

enum ArticleTableActions {
  View = 'View',         // 檢視
  Update = 'Update',     // 編輯
  Delete = 'Delete',     // 刪除
  Submit = 'Submit',     // 送審
  PutBack = 'PutBack',   // 退回
  Review = 'Review',     // 審核
  Release = 'Release',   // 發布
  Withdraw = 'Withdraw', // 撤下
}

VerifyReleaseModalRadio

enum VerifyReleaseModalRadio {
  Now = 'Now',           // 立即發佈
  Schedule = 'Schedule', // 預約發佈
  Approve = 'Approve',   // 即刻通過
  Reject = 'Reject',     // 不通過
}

Default Permission Presets

import {
  defaultAdminRolePermissions,
  defaultGeneralRolePermissions,
  defaultTableActions,
} from '@rytass/cms-react-components';

// Admin 角色預設權限(完整權限)
// 包含所有階段的 Update/Delete/Release/Withdraw 權限

// 一般角色預設權限(有限權限)
// 主要包含 Create、Draft 編輯、送審、部分發布權限

// 預設表格操作(按階段)
// DRAFT: [Update, Submit, Release, Delete]
// REVIEWING: [Update, Review, Delete, PutBack]
// VERIFIED: [View, Update, Release, Delete]
// SCHEDULED: [View, Update, Withdraw]
// RELEASED: [Update, Delete]

Utilities

havePermission

權限檢查工具函式:

import { havePermission, ArticlesPermissions } from '@rytass/cms-react-components';

const canDelete = havePermission({
  userPermissions: userPermissions,
  targetPermission: ArticlesPermissions.DeleteArticleInDraft,
});

if (canDelete) {
  // 顯示刪除按鈕
}

VersionLog Icon

版本日誌圖示元件:

import { VersionLog } from '@rytass/cms-react-components';

<VersionLog width={24} height={24} />

Hooks

useDialog

import { useDialog } from '@rytass/cms-react-components';

const { openDialog, closeDialog } = useDialog();

openDialog({
  title: '提示',
  content: '操作成功!',
  onConfirm: closeDialog,
});

useModal

import { useModal } from '@rytass/cms-react-components';

const { openModal, closeModal } = useModal();

openModal({
  children: <YourModalComponent prop1="value" />,
  // 可選配置(繼承 Mezzanine UI ModalProps)
  size: 'medium',                    // 'small' | 'medium' | 'large' | 'extraLarge'
  width: 600,                        // 自訂寬度 (px)
  severity: 'warning',               // 'success' | 'warning' | 'error'
  hideCloseIcon: true,               // 是否隱藏關閉按鈕(預設 true)
  disableCloseOnBackdropClick: false, // 是否禁止點擊背景關閉(預設 false)
  className: 'my-modal',             // 自訂 className
  onClose: () => {},                 // 關閉時回調
});

ModalConfigType:

屬性 型別 預設值 說明
children React.JSX.Element - Modal 內容(傳入 JSX)
size ModalSize 'medium' Modal 尺寸
width number - 自訂寬度 (px)
severity string - 嚴重程度圖示
hideCloseIcon boolean true 隱藏關閉按鈕
disableCloseOnBackdropClick boolean false 禁止點擊背景關閉
className string - 自訂 className
onClose () => void - 關閉時回調

Complete Example

import {
  DialogProvider,
  ModalProvider,
  StandardCMSList,           // 整合版(Tabs + Table)
  StandardCMSTable,
  StandardCMSTabs,
  useModal,
  DeleteWithdrawModal,
  DeleteWithdrawModalRadio,  // 刪除/撤下選項
  RejectModal,
  VerifyReleaseModal,
  VerifyReleaseModalRadio,
  ArticleStage,
  ArticlesPermissions,
  ArticleTableActions,
  defaultAdminRolePermissions,  // 預設權限集
  defaultGeneralRolePermissions,
  defaultTableActions,
} from '@rytass/cms-react-components';

// App wrapper
function App() {
  return (
    <DialogProvider>
      <ModalProvider>
        <ArticleManagement />
      </ModalProvider>
    </DialogProvider>
  );
}

// 方法一:使用 StandardCMSList(推薦)
function ArticleManagementSimple() {
  const { openModal, closeModal } = useModal();

  return (
    <StandardCMSList<Article>
      columns={columns}
      dataSource={articles}
      defaultStage={ArticleStage.DRAFT}
      userPermissions={defaultAdminRolePermissions}
      onTabChange={(stage) => fetchArticles(stage)}
      actionsEvents={{
        onView: async (article) => router.push(`/articles/${article.id}`),
        onSubmit: async (article) => await submitArticle(article.id),
        onDelete: async (article) => await deleteArticle(article.id),
        onRelease: async (article, releasedAt) => {
          await releaseArticle(article.id, releasedAt);
        },
        onApprove: async (article) => await approveArticle(article.id),
        onReject: async (article, reason) => await rejectArticle(article.id, reason),
      }}
    />
  );
}

// 方法二:分開使用 Tabs + Table(更靈活)
function ArticleManagement() {
  const { openModal, closeModal } = useModal();
  const [articles, setArticles] = useState<Article[]>([]);
  const [currentStage, setCurrentStage] = useState(ArticleStage.DRAFT);

  // 使用細顆粒度權限
  const userPermissions = [
    ArticlesPermissions.CreateArticle,
    ArticlesPermissions.SubmitPutBackArticle,
    ArticlesPermissions.ApproveRejectArticle,
    // Draft
    ArticlesPermissions.UpdateArticleInDraft,
    ArticlesPermissions.DeleteArticleInDraft,
    // Reviewing
    ArticlesPermissions.UpdateArticleInReviewing,
    // Released
    ArticlesPermissions.UpdateArticleInReleased,
    ArticlesPermissions.ReleaseArticleInReleased,
  ];

  const handleDelete = (article: Article) => {
    openModal({
      children: (
        <DeleteWithdrawModal
          defaultRadioValue={DeleteWithdrawModalRadio.Delete}
          withDelete={true}
          withWithdraw={currentStage === ArticleStage.RELEASED}
          onDelete={async () => {
            await deleteArticle(article.id);
            refreshArticles();
          }}
          onWithdraw={async () => {
            await withdrawArticle(article.id);
            refreshArticles();
          }}
        />
      ),
    });
  };

  const handleRelease = (article: Article) => {
    openModal({
      children: (
        <VerifyReleaseModal
          title="發佈文章"
          withApprove={currentStage === ArticleStage.REVIEWING}
          withReject={currentStage === ArticleStage.REVIEWING}
          onRelease={async (releasedAt) => {
            await releaseArticle(article.id, releasedAt);
            refreshArticles();
            closeModal();
          }}
          onApprove={async () => {
            await approveArticle(article.id);
            refreshArticles();
            closeModal();
          }}
          onReject={async (reason) => {
            await rejectArticle(article.id, reason);
            refreshArticles();
            closeModal();
          }}
        />
      ),
    });
  };

  return (
    <div>
      <StandardCMSTabs
        activeStage={currentStage}
        onChange={(stage) => {
          setCurrentStage(stage);
          fetchArticles(stage);
        }}
      />

      <StandardCMSTable<Article>
        columns={columns}
        dataSource={articles}
        currentStage={currentStage}
        userPermissions={userPermissions}
        actions={defaultTableActions}
        actionsEvents={{
          onView: async (article) => router.push(`/articles/${article.id}`),
          onDelete: handleDelete,
          onSubmit: async (article) => {
            await submitArticle(article.id);
            refreshArticles();
          },
          onPutBack: async (article) => {
            await putBackArticle(article.id);
            refreshArticles();
          },
          onRelease: handleRelease,
          onWithdraw: async (article) => {
            await withdrawArticle(article.id);
            refreshArticles();
          },
          onApprove: async (article) => {
            await approveArticle(article.id);
            refreshArticles();
          },
          onReject: async (article, reason) => {
            await rejectArticle(article.id, reason);
            refreshArticles();
          },
        }}
      />
    </div>
  );
}

Dependencies

Peer Dependencies:

  • @mezzanine-ui/core
  • @mezzanine-ui/react
  • @mezzanine-ui/icons
  • react, react-dom
  • react-hook-form
  • dayjs
  • lodash

Troubleshooting

Modal 不顯示

確保在應用根層級包裝 ModalProvider

<ModalProvider>
  <App />
</ModalProvider>

權限按鈕不顯示

確認 userPermissions 陣列包含對應的細顆粒度權限

// 錯誤:使用簡化權限(不存在)
userPermissions={[ArticlesPermissions.DELETE]}  // ❌ 不存在

// 正確:使用按階段細分的權限
userPermissions={[
  ArticlesPermissions.DeleteArticleInDraft,     // ✓ Draft 階段刪除
  ArticlesPermissions.DeleteArticleInReleased,  // ✓ Released 階段刪除
]}

// 或使用預設權限集
userPermissions={defaultAdminRolePermissions}  // ✓ 完整權限
Weekly Installs
5
Repository
rytass/utils
GitHub Stars
6
First Seen
Feb 5, 2026
Installed on
amp5
github-copilot5
replit5
codex5
kimi-cli5
gemini-cli5