feat: 完成權限管理系統、統一頁面標題樣式與表格對齊規範

This commit is contained in:
2026-01-13 13:30:51 +08:00
parent 6770a4ec2f
commit ecfcbb93ed
28 changed files with 2333 additions and 34 deletions

View File

@@ -0,0 +1,84 @@
import { usePermission } from '@/hooks/usePermission';
import { ReactNode } from 'react';
interface CanProps {
permission: string | string[];
children: ReactNode;
fallback?: ReactNode;
}
/**
* 權限判斷元件 - 類似 Blade 的 @can 指令
*
* @example
* ```tsx
* <Can permission="products.create">
* <button>新增商品</button>
* </Can>
*
* <Can permission={['products.edit', 'products.delete']}>
* <div>管理操作</div>
* </Can>
* ```
*/
export function Can({ permission, children, fallback = null }: CanProps) {
const { can, canAny } = usePermission();
const hasPermission = Array.isArray(permission)
? canAny(permission)
: can(permission);
return hasPermission ? <>{children}</> : <>{fallback}</>;
}
interface HasRoleProps {
role: string | string[];
children: ReactNode;
fallback?: ReactNode;
}
/**
* 角色判斷元件 - 類似 Blade 的 @role 指令
*
* @example
* ```tsx
* <HasRole role="admin">
* <Link href="/admin">管理後台</Link>
* </HasRole>
*
* <HasRole role={['admin', 'manager']}>
* <button>管理選項</button>
* </HasRole>
* ```
*/
export function HasRole({ role, children, fallback = null }: HasRoleProps) {
const { hasRole, hasAnyRole } = usePermission();
const hasRequiredRole = Array.isArray(role)
? hasAnyRole(role)
: hasRole(role);
return hasRequiredRole ? <>{children}</> : <>{fallback}</>;
}
interface CanAllProps {
permissions: string[];
children: ReactNode;
fallback?: ReactNode;
}
/**
* 檢查是否擁有所有權限
*
* @example
* ```tsx
* <CanAll permissions={['products.edit', 'products.delete']}>
* <button>完整管理</button>
* </CanAll>
* ```
*/
export function CanAll({ permissions, children, fallback = null }: CanAllProps) {
const { canAll } = usePermission();
return canAll(permissions) ? <>{children}</> : <>{fallback}</>;
}

View File

@@ -20,7 +20,7 @@ export function PurchaseOrderActions({
};
return (
<div className="flex justify-end gap-2">
<div className="flex justify-center gap-2">
<Link href={`/purchase-orders/${order.id}`}>
<Button
variant="outline"

View File

@@ -174,7 +174,7 @@ export default function PurchaseOrderTable({
<SortIcon field="status" />
</button>
</TableHead>
<TableHead className="text-right font-semibold"></TableHead>
<TableHead className="text-center font-semibold"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -214,7 +214,7 @@ export default function PurchaseOrderTable({
<TableCell>
<PurchaseOrderStatusBadge status={order.status} />
</TableCell>
<TableCell>
<TableCell className="text-center">
<PurchaseOrderActions
order={order}
/>