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:
2026-01-28 12:57:36 +08:00
parent e3e5e89df6
commit 7948b1afc7
50 changed files with 9506 additions and 150 deletions

View File

@@ -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>
);
}