baseline: Clover 独立仓库首次基线提交

将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。
当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包),
M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-16 11:30:22 +08:00
commit 6a2632da70
253 changed files with 27467 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
'use client';
/**
* AuthGuard — 路由守卫
* - 未登录 → /login
* - 越权(组长访问 /config非管理员等→ /dashboard
* 扒 banana AuthGuard 范式,全新重建
*/
import { useEffect } from 'react';
import { useRouter, usePathname } from 'next/navigation';
import { useAuthStore } from '@/stores/authStore';
import { UserRole } from '@/types/index';
// 路由 → 最低所需角色
const ROUTE_ROLES: Record<string, UserRole[]> = {
'/config': ['admin'],
'/review': ['admin', 'supervisor'],
'/history': ['admin', 'supervisor', 'operator'],
'/tasks/new': ['admin', 'operator'],
'/dashboard': ['admin', 'supervisor', 'operator'],
};
function getRequiredRoles(pathname: string): UserRole[] | null {
// 精确匹配或前缀匹配(如 /tasks/[id]/text
if (ROUTE_ROLES[pathname]) return ROUTE_ROLES[pathname];
for (const [prefix, roles] of Object.entries(ROUTE_ROLES)) {
if (pathname.startsWith(prefix + '/')) return roles;
}
return null; // 公开路由(/login 等)
}
interface AuthGuardProps {
children: React.ReactNode;
}
export function AuthGuard({ children }: AuthGuardProps) {
const router = useRouter();
const pathname = usePathname();
const { isAuthenticated, role, isLoading, fetchMe, endLoading } = useAuthStore();
useEffect(() => {
// 首屏校验:有 token 且 store 未初始化 → 拉 me无 token → 结束 loading 让守卫放行判断
const token = typeof window !== 'undefined' ? localStorage.getItem('clover_token') : null;
if (token && !isAuthenticated && isLoading) {
fetchMe();
} else if (!token && isLoading) {
endLoading();
}
}, [isAuthenticated, isLoading, fetchMe, endLoading]);
useEffect(() => {
if (isLoading) return;
// 公开路由直接放行
if (pathname === '/login') return;
if (!isAuthenticated) {
router.replace('/login');
return;
}
const required = getRequiredRoles(pathname);
if (required && role && !required.includes(role)) {
router.replace('/dashboard');
}
}, [isAuthenticated, role, isLoading, pathname, router]);
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen bg-surface-secondary">
<div className="text-text-secondary text-sm"></div>
</div>
);
}
return <>{children}</>;
}