# Users Management Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 重新实现 admin/users 模块,提供多条件筛选、批量操作、详情抽屉编辑功能 **Architecture:** 后端扩展用户 API 支持状态/角色筛选、排序、批量操作;前端重写页面组件,采用筛选栏 + 多选表格 + 批量操作栏 + 详情抽屉的布局 **Tech Stack:** Nuxt 3, TypeScript, Tailwind (CSS variables from DESIGN.md), useHttpFetch composable --- ## File Structure ``` server/api/users/ index.get.ts — 修改:增加 status, role, sortBy, sortOrder 筛选 [id].put.ts — 新增:更新单个用户 batch.post.ts — 新增:批量操作(启用/禁用/删除) app/pages/admin/users/ index.vue — 重写:完整用户管理界面 ``` --- ## Task 1: Extend GET /api/users with filtering & sorting **Files:** - Modify: `server/api/users/index.get.ts` - [ ] **Step 1: Read current implementation** Review `server/api/users/index.get.ts` (lines 1-50 already read) - [ ] **Step 2: Add query parameters for status, role, sortBy, sortOrder** ```typescript // Add after line 9 (after search) const status = query.status as string | undefined; const role = query.role as string | undefined; const sortBy = query.sortBy as string | undefined; const sortOrder = query.sortOrder as "asc" | "desc" | undefined; ``` - [ ] **Step 3: Build where clause** ```typescript // Add after line 11 (after searchPattern) const conditions = searchPattern ? or( like(users.username, searchPattern), like(users.email, searchPattern), like(users.nickname, searchPattern), ) : undefined; if (status && status !== "all") { conditions && conditions.length ? (conditions.push(eq(users.status, status as "active" | "disabled"))) : undefined; } if (role && role !== "all") { conditions ? undefined : undefined; } ``` Actually, use drizzle's `and` helper: ```typescript import { and, eq } from "drizzle-orm"; // Replace the where clause building (lines 30-38) with: const whereConditions = []; if (searchPattern) { whereConditions.push( or( like(users.username, searchPattern), like(users.email, searchPattern), like(users.nickname, searchPattern), ) ); } if (status && status !== "all") { whereConditions.push(eq(users.status, status as "active" | "disabled")); } if (role && role !== "all") { whereConditions.push(eq(users.role, role as "admin" | "user")); } const whereClause = whereConditions.length > 0 ? and(...whereConditions) : undefined; ``` - [ ] **Step 4: Add sorting logic** ```typescript // Add before dbGlobal.select (before line 18) const orderColumn = sortBy === "username" ? users.username : sortBy === "createdAt" ? users.createdAt : users.createdAt; const orderDirection = sortOrder === "asc" ? orderColumn : desc(orderColumn); ``` - [ ] **Step 5: Update query to use whereClause and orderBy** Replace `.where(searchPattern ? or(...) : undefined)` with `.where(whereClause)` Replace `.orderBy(desc(users.createdAt))` with `.orderBy(orderDirection)` - [ ] **Step 6: Commit** ```bash git add server/api/users/index.get.ts git commit -m "feat(users): add status, role filtering and sorting to GET /api/users" ``` --- ## Task 2: Create PUT /api/users/:id endpoint **Files:** - Create: `server/api/users/[id].put.ts` - [ ] **Step 1: Create the file** ```typescript import { dbGlobal } from "drizzle-pkg/lib/db"; import { users } from "drizzle-pkg/lib/schema/auth"; import { eq } from "drizzle-orm"; import log4js from "logger"; const logger = log4js.getLogger("USERS"); export default defineWrappedResponseHandler(async (event) => { const id = Number(event.context.params?.id); const body = await readBody(event); if (!id || isNaN(id)) { throw createError({ statusCode: 400, statusMessage: "无效的用户ID", }); } if (body.email !== undefined && body.email !== "" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(body.email)) { throw createError({ statusCode: 400, statusMessage: "邮箱格式不正确", }); } if (body.role && !["admin", "user"].includes(body.role)) { throw createError({ statusCode: 400, statusMessage: "无效的角色", }); } if (body.status && !["active", "disabled"].includes(body.status)) { throw createError({ statusCode: 400, statusMessage: "无效的状态", }); } const [existing] = await dbGlobal .select({ id: users.id }) .from(users) .where(eq(users.id, id)); if (!existing) { throw createError({ statusCode: 404, statusMessage: "用户不存在", }); } const updateData: Partial<{ nickname: string | null; email: string | null; role: "admin" | "user"; status: "active" | "disabled"; }> = {}; if (body.nickname !== undefined) updateData.nickname = body.nickname || null; if (body.email !== undefined) updateData.email = body.email || null; if (body.role !== undefined) updateData.role = body.role; if (body.status !== undefined) updateData.status = body.status; const [updated] = await dbGlobal .update(users) .set(updateData) .where(eq(users.id, id)) .returning({ id: users.id, username: users.username, email: users.email, nickname: users.nickname, avatar: users.avatar, role: users.role, status: users.status, createdAt: users.createdAt, }); logger.info("user updated by admin: %s (id: %d)", updated.username, id); return R.success(updated); }); ``` - [ ] **Step 2: Commit** ```bash git add server/api/users/[id].put.ts git commit -m "feat(users): add PUT /api/users/:id endpoint" ``` --- ## Task 3: Create POST /api/users/batch endpoint **Files:** - Create: `server/api/users/batch.post.ts` - [ ] **Step 1: Create the file** ```typescript import { dbGlobal } from "drizzle-pkg/lib/db"; import { users } from "drizzle-pkg/lib/schema/auth"; import { eq, inArray } from "drizzle-orm"; import log4js from "logger"; const logger = log4js.getLogger("USERS"); export default defineWrappedResponseHandler(async (event) => { const body = await readBody(event); if (!body?.ids || !Array.isArray(body.ids) || body.ids.length === 0) { throw createError({ statusCode: 400, statusMessage: "请选择要操作的用户", }); } if (!body?.action || !["enable", "disable", "delete"].includes(body.action)) { throw createError({ statusCode: 400, statusMessage: "无效的操作类型", }); } const ids = body.ids.map(Number).filter(n => !isNaN(n)); if (ids.length === 0) { throw createError({ statusCode: 400, statusMessage: "无效的用户ID列表", }); } if (body.action === "delete") { await dbGlobal.delete(users).where(inArray(users.id, ids)); logger.info("users batch deleted by admin: count=%d", ids.length); return R.success({ message: `已删除 ${ids.length} 个用户` }); } const newStatus = body.action === "enable" ? "active" : "disabled"; await dbGlobal .update(users) .set({ status: newStatus }) .where(inArray(users.id, ids)); logger.info("users batch %s by admin: ids=%s", body.action, ids.join(",")); return R.success({ message: `已${body.action === "enable" ? "启用" : "禁用"} ${ids.length} 个用户` }); }); ``` - [ ] **Step 2: Commit** ```bash git add server/api/users/batch.post.ts git commit -m "feat(users): add POST /api/users/batch for batch operations" ``` --- ## Task 4: Rewrite users/index.vue page **Files:** - Modify: `app/pages/admin/users/index.vue` This is the main frontend task. Follow the spec exactly: - Filter bar with status tabs, role dropdown, search - Stats cards (total, admin, disabled) - Sortable table with checkboxes - Batch action bar - Detail drawer - Create modal - Confirm dialog - [ ] **Step 1: Read current implementation** Already read (320 lines) — use as reference for structure - [ ] **Step 2: Write new implementation** Complete rewrite with all features from spec: - State tabs (全部/正常/禁用) using pill-style buttons - Role dropdown select - Search input with icon - Sortable table columns (username, createdAt) - Checkbox multi-select with select-all - Stats cards row - Batch bar slides in when selection > 0 - Detail drawer from right side (480px wide) - Create modal centered - Confirm dialog for batch delete Use DESIGN.md tokens: - `var(--color-primary)` for coral buttons - `var(--color-accent-teal)` for enable actions - `var(--color-error)` for delete actions - `var(--color-surface-card)` for cards - `var(--color-hairline)` for borders - `var(--rounded-md)` (8px) for buttons/inputs - `var(--rounded-lg)` (12px) for cards - [ ] **Step 3: Test the page** Start dev server `npm run dev` and verify: - [ ] Filter by status works - [ ] Filter by role works - [ ] Search works - [ ] Table sorting works (click username/createdAt headers) - [ ] Checkbox selection works - [ ] Batch bar appears when items selected - [ ] Batch enable/disable works - [ ] Batch delete shows confirm dialog - [ ] Create modal works - [ ] Edit drawer opens and saves - [ ] **Step 4: Commit** ```bash git add app/pages/admin/users/index.vue git commit -m "feat(users): complete rewrite with filtering, sorting, batch operations" ``` --- ## Verification Checklist - [ ] GET /api/users supports `status`, `role`, `sortBy`, `sortOrder` params - [ ] PUT /api/users/:id updates nickname, email, role, status - [ ] POST /api/users/batch handles enable/disable/delete - [ ] Page shows filter bar with status tabs + role dropdown + search - [ ] Page shows stats cards (total/admin/disabled) - [ ] Table columns are sortable (username, createdAt) - [ ] Checkbox multi-select works with select-all - [ ] Batch action bar shows enable/disable/delete buttons - [ ] Detail drawer opens on edit and saves changes - [ ] Create modal adds new user - [ ] Batch delete shows confirm dialog