feat: 实现用户登录认证与权限控制系统
- 新增用户登录页面与API端点(/api/auth/login、/api/auth/logout、/api/auth/me) - 引入基于密码哈希的认证机制,支持默认管理员账号登录 - 创建访问控制模块(lib/access-control.ts),实现基于角色的权限验证 - 在现有API端点中集成权限检查,限制非管理员用户只能访问其所属项目数据 - 更新应用布局,要求用户登录后才能访问主界面 - 扩展用户表结构,增加用户名和密码哈希字段 - 调整项目、KR、目标、会议记录等资源的查询逻辑,根据用户权限过滤数据 - 改进管理页面(指标、模板)的删除操作,使用确认对话框替代直接确认 - 优化“我的KR”页面,为管理员提供全员KR概览视图 - 修复.gitignore文件中的拼写错误(.trae → .travis) - 添加数据库迁移脚本,支持任务直接关联项目、禁用RLS、添加求助字段等 - 配置Next.js以支持独立输出和子路径部署
This commit is contained in:
@@ -19,10 +19,22 @@ interface KRWithRelations extends KR {
|
||||
}
|
||||
|
||||
export default function MyKRsPage() {
|
||||
const { currentUser } = useUser();
|
||||
const { currentUser, users } = useUser();
|
||||
const [krs, setKRs] = useState<KRWithRelations[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// Group KRs by owner for owner view
|
||||
const krsByOwner = users.map(user => ({
|
||||
user,
|
||||
krs: krs.filter(kr => kr.owner_user_id === user.id && isKRActive(kr))
|
||||
})).filter(group => group.krs.length > 0 || group.user.role !== 'owner') // Filter out users with no active KRs unless it's a member/pm we want to track
|
||||
.sort((a, b) => {
|
||||
// Sort by KR count descending (users with KRs first)
|
||||
if (a.krs.length > 0 && b.krs.length === 0) return -1;
|
||||
if (a.krs.length === 0 && b.krs.length > 0) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchKRs() {
|
||||
try {
|
||||
@@ -46,6 +58,8 @@ export default function MyKRsPage() {
|
||||
const completedKRs = activeKRs.filter(kr => calculateProgress(kr) >= 1);
|
||||
const pastKRs = myKRs.filter(kr => !isKRActive(kr));
|
||||
|
||||
const isOwner = currentUser?.role === 'owner';
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto px-4 py-8">
|
||||
@@ -64,13 +78,54 @@ export default function MyKRsPage() {
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto px-4 py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-slate-800">我的 KR</h1>
|
||||
<h1 className="text-2xl font-bold text-slate-800">
|
||||
{isOwner ? '全员 KR 概览' : '我的 KR'}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
{currentUser?.name} 负责的执行目标
|
||||
{isOwner
|
||||
? '查看所有成员的当前周期目标执行情况'
|
||||
: `${currentUser?.name} 负责的执行目标`
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4 mb-8">
|
||||
{isOwner ? (
|
||||
<div className="space-y-8">
|
||||
{krsByOwner.map(({ user, krs }) => (
|
||||
<div key={user.id} className="space-y-4">
|
||||
<div className="flex items-center gap-3 border-b pb-2">
|
||||
<div className="h-8 w-8 rounded-full bg-slate-200 flex items-center justify-center text-sm font-medium text-slate-600">
|
||||
{user.name[0]}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-slate-800">{user.name}</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{krs.length} 个进行中的 KR
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{krs.length === 0 ? (
|
||||
<div className="p-8 text-center bg-slate-50 rounded-lg border border-dashed">
|
||||
<p className="text-muted-foreground text-sm">暂无进行中的 KR</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{krs.map(kr => <KRCard key={kr.id} kr={kr} />)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{krsByOwner.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-muted-foreground">没有任何成员有正在进行的 KR</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid grid-cols-3 gap-4 mb-8">
|
||||
<Card>
|
||||
<CardContent className="pt-6 text-center">
|
||||
<Target className="h-8 w-8 text-slate-300 mx-auto mb-2" />
|
||||
@@ -142,6 +197,8 @@ export default function MyKRsPage() {
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user