From 7948b1afc7793d47d7cf115b28cd3cfe8189fec6 Mon Sep 17 00:00:00 2001 From: karlcarlo Date: Wed, 28 Jan 2026 12:57:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E8=AE=A4=E8=AF=81=E4=B8=8E=E6=9D=83=E9=99=90?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增用户登录页面与API端点(/api/auth/login、/api/auth/logout、/api/auth/me) - 引入基于密码哈希的认证机制,支持默认管理员账号登录 - 创建访问控制模块(lib/access-control.ts),实现基于角色的权限验证 - 在现有API端点中集成权限检查,限制非管理员用户只能访问其所属项目数据 - 更新应用布局,要求用户登录后才能访问主界面 - 扩展用户表结构,增加用户名和密码哈希字段 - 调整项目、KR、目标、会议记录等资源的查询逻辑,根据用户权限过滤数据 - 改进管理页面(指标、模板)的删除操作,使用确认对话框替代直接确认 - 优化“我的KR”页面,为管理员提供全员KR概览视图 - 修复.gitignore文件中的拼写错误(.trae → .travis) - 添加数据库迁移脚本,支持任务直接关联项目、禁用RLS、添加求助字段等 - 配置Next.js以支持独立输出和子路径部署 --- .gitignore | 2 + app/admin/metrics/page.tsx | 51 +- app/admin/templates/page.tsx | 51 +- app/admin/users/page.tsx | 107 +- app/api/auth/login/route.ts | 117 + app/api/auth/logout/route.ts | 15 + app/api/auth/me/route.ts | 27 + app/api/checkins/route.ts | 56 +- app/api/krs/[id]/route.ts | 23 + app/api/krs/route.ts | 30 + app/api/meetings/[id]/route.ts | 49 + app/api/meetings/route.ts | 32 +- app/api/metrics/[id]/route.ts | 28 +- app/api/metrics/route.ts | 15 +- app/api/objectives/[id]/route.ts | 23 + app/api/objectives/route.ts | 32 +- app/api/projects/[id]/members/route.ts | 22 + app/api/projects/[id]/route.ts | 22 + app/api/projects/route.ts | 27 +- app/api/task-steps/route.ts | 240 + app/api/tasks/[id]/route.ts | 118 + app/api/tasks/route.ts | 189 + app/api/templates/[id]/route.ts | 28 +- app/api/templates/route.ts | 15 +- app/api/users/[id]/route.ts | 58 +- app/api/users/route.ts | 71 +- app/krs/[id]/client-page.tsx | 199 +- app/layout.tsx | 2 +- app/login/page.tsx | 80 + app/my-krs/page.tsx | 65 +- app/my-tasks/page.tsx | 485 ++ app/page.tsx | 2 +- app/projects/[id]/client-page.tsx | 356 +- app/projects/[id]/krs/new/page.tsx | 28 +- components/app-shell.tsx | 40 +- components/init-wizard.tsx | 2 +- components/main-nav.tsx | 73 +- components/status-badge.tsx | 5 +- components/task-edit-dialog.tsx | 741 +++ components/user-context.tsx | 81 +- lib/access-control.ts | 73 + lib/password.ts | 25 + next.config.js | 8 + pnpm-lock.yaml | 5746 +++++++++++++++++ .../20260126153000_create_tasks_tables.sql | 68 + ...20260127100000_add_project_id_to_tasks.sql | 10 + .../migrations/add_help_request_fields.sql | 56 + supabase/migrations/add_user_login_fields.sql | 15 + .../disable_rls_tasks_and_steps.sql | 2 + types/database.ts | 46 + 50 files changed, 9506 insertions(+), 150 deletions(-) create mode 100644 app/api/auth/login/route.ts create mode 100644 app/api/auth/logout/route.ts create mode 100644 app/api/auth/me/route.ts create mode 100644 app/api/task-steps/route.ts create mode 100644 app/api/tasks/[id]/route.ts create mode 100644 app/api/tasks/route.ts create mode 100644 app/login/page.tsx create mode 100644 app/my-tasks/page.tsx create mode 100644 components/task-edit-dialog.tsx create mode 100644 lib/access-control.ts create mode 100644 lib/password.ts create mode 100644 pnpm-lock.yaml create mode 100644 supabase/migrations/20260126153000_create_tasks_tables.sql create mode 100644 supabase/migrations/20260127100000_add_project_id_to_tasks.sql create mode 100644 supabase/migrations/add_help_request_fields.sql create mode 100644 supabase/migrations/add_user_login_fields.sql create mode 100644 supabase/migrations/disable_rls_tasks_and_steps.sql diff --git a/.gitignore b/.gitignore index 9b1913e..ab72c11 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +.trae diff --git a/app/admin/metrics/page.tsx b/app/admin/metrics/page.tsx index 0a86390..08dd68c 100644 --- a/app/admin/metrics/page.tsx +++ b/app/admin/metrics/page.tsx @@ -18,6 +18,16 @@ import type { Metric } from '@/types/database'; import { toast } from 'sonner'; import { Plus, Pencil, Trash2, Gauge, Loader2, TrendingUp, TrendingDown } from 'lucide-react'; import { getErrorMessage } from '@/lib/error-utils'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; const metricSchema = z.object({ name: z.string().min(1, '名称不能为空'), @@ -35,6 +45,9 @@ export default function AdminMetricsPage() { const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingMetric, setEditingMetric] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); + const [deleteMetricId, setDeleteMetricId] = useState(null); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); const form = useForm({ resolver: zodResolver(metricSchema), @@ -121,17 +134,26 @@ export default function AdminMetricsPage() { } }; - const handleDelete = async (metricId: string) => { - if (!confirm('确定要删除此指标吗?')) return; + const requestDelete = (metricId: string) => { + setDeleteMetricId(metricId); + setIsDeleteOpen(true); + }; + const confirmDelete = async () => { + if (!deleteMetricId) return; + setIsDeleting(true); try { - const res = await fetch(`/api/metrics/${metricId}`, { method: 'DELETE' }); + const res = await fetch(`/api/metrics/${deleteMetricId}`, { method: 'DELETE' }); if (!res.ok) throw new Error('删除失败'); toast.success('指标已删除'); fetchMetrics(); } catch (error) { toast.error(getErrorMessage(error)); + } finally { + setIsDeleting(false); + setIsDeleteOpen(false); + setDeleteMetricId(null); } }; @@ -309,7 +331,7 @@ export default function AdminMetricsPage() { - @@ -327,6 +349,27 @@ export default function AdminMetricsPage() { + + + + + 确认删除指标? + + 删除后无法恢复。 + + + + 取消 + + {isDeleting ? '删除中...' : '确认删除'} + + + + ); } diff --git a/app/admin/templates/page.tsx b/app/admin/templates/page.tsx index 80ee337..102ec82 100644 --- a/app/admin/templates/page.tsx +++ b/app/admin/templates/page.tsx @@ -18,6 +18,16 @@ import type { Template } from '@/types/database'; import { toast } from 'sonner'; import { Plus, Pencil, Trash2, Files, Loader2, X } from 'lucide-react'; import { getErrorMessage } from '@/lib/error-utils'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; const templateItemSchema = z.object({ title: z.string().min(1, '标题不能为空'), @@ -44,6 +54,9 @@ export default function AdminTemplatesPage() { const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingTemplate, setEditingTemplate] = useState