feat(client): 初始化前端壳与鉴权守卫(P1-5)
- 技术栈:React 18 + TypeScript + Vite + Tailwind - API 层:ApiError + 401 自动刷新 + single-flight 重试 - 鉴权:内存 Access Token + Cookie Refresh + 启动 bootstrap - RBAC:权限映射 + RequireAuth/RequirePermission 路由守卫 - 布局:OA 工作台壳层 + 侧边栏 + 5 个占位页面 - 样式:复用 hyb-web 视觉 token(单向同步) - 文档:新增 client README,补充 server 联调说明
这个提交包含在:
@@ -0,0 +1,17 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
type EmptyStateCardProps = {
|
||||
title: string;
|
||||
description: string;
|
||||
footer?: ReactNode;
|
||||
};
|
||||
|
||||
export function EmptyStateCard({ title, description, footer }: EmptyStateCardProps) {
|
||||
return (
|
||||
<section className="card-std">
|
||||
<h3 className="card-title">{title}</h3>
|
||||
<p className="card-desc">{description}</p>
|
||||
{footer ? <div className="mt-4">{footer}</div> : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
type PageHeaderProps = {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
actions?: ReactNode;
|
||||
};
|
||||
|
||||
export function PageHeader({ title, subtitle, actions }: PageHeaderProps) {
|
||||
return (
|
||||
<div className="mb-6 flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-slate-900">{title}</h2>
|
||||
{subtitle ? <p className="mt-1 text-sm text-secondary">{subtitle}</p> : null}
|
||||
</div>
|
||||
|
||||
{actions ? <div className="shrink-0">{actions}</div> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户