10 KiB
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
// 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
// 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:
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
// 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
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
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
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
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
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
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,sortOrderparams - 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