Просмотр исходного кода

feat(admin): add users list Excel export endpoint

Reuse list filters, tenant and data scope; support ids and visible columns.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 2 месяцев назад
Родитель
Сommit
eb4233f23f
1 измененных файлов с 102 добавлено и 0 удалено
  1. 102 0
      backend/app/api/admin.py

+ 102 - 0
backend/app/api/admin.py

@@ -1,6 +1,7 @@
 """运营端管理接口(租户、角色下拉等)"""
 import os
 import uuid
+from datetime import datetime
 from typing import Any, List, Optional
 
 from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File, Form
@@ -35,6 +36,12 @@ from app.schemas.menu_favorite import (
     MenuFavoriteReorderBody,
 )
 from app.services.web_auth_roles import _map_role_name_to_web
+from app.utils.excel_export import (
+    ExportColumn,
+    ExportValidationError,
+    export_rows_to_xlsx_response,
+    filter_export_columns,
+)
 
 router = APIRouter(prefix="/web/admin", tags=["运营端管理"])
 
@@ -277,6 +284,26 @@ class UserUpdateBody(BaseModel):
     post_ids: Optional[List[str]] = Field(None, alias="postIds")
 
 
+USER_EXPORT_COLUMNS = [
+    ExportColumn(title="用户账号", field="userNo"),
+    ExportColumn(title="用户名", field="nickName"),
+    ExportColumn(title="所属组织", field="deptName"),
+    ExportColumn(title="角色", field="roles"),
+    ExportColumn(title="手机号", field="phoneNo"),
+    ExportColumn(title="状态", field="status", dict_type="sys_status"),
+    ExportColumn(title="创建时间", field="createTime"),
+]
+
+
+class UserExportBody(BaseModel):
+    model_config = ConfigDict(populate_by_name=True)
+    keyword: Optional[str] = None
+    dept_id: Optional[str] = Field(None, alias="deptId")
+    status: Optional[str] = None
+    ids: Optional[List[str]] = None
+    columns: Optional[List[str]] = None
+
+
 @router.get("/users")
 def list_users(
     page: int = Query(1, ge=1),
@@ -335,6 +362,81 @@ def list_users(
     return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
 
 
+@router.post("/users/export")
+def export_users(
+    body: UserExportBody,
+    user: dict = Depends(require_menu_code("system:user:list")),
+    db: Session = Depends(get_db),
+):
+    """导出用户列表(当前筛选全量,或 ids 已选)。"""
+    try:
+        tenant_no = _get_tenant_no(db, user)
+        query = db.query(SysUser).filter(
+            SysUser.del_flag == 0,
+            SysUser.tenant_no == tenant_no,
+        )
+        kw = (body.keyword or "").strip()
+        if kw:
+            like = f"%{kw}%"
+            query = query.filter(
+                or_(
+                    SysUser.user_no.like(like),
+                    SysUser.nick_name.like(like),
+                    SysUser.phone_no.like(like),
+                )
+            )
+        if body.dept_id:
+            query = query.filter(SysUser.dept_id == body.dept_id)
+        if body.status:
+            query = query.filter(SysUser.status == body.status)
+        if body.ids:
+            query = query.filter(SysUser.id.in_(body.ids))
+        query = apply_data_scope(query, SysUser, user, db, tenant_no)
+        rows_orm = query.order_by(SysUser.create_time.desc()).all()
+        dept_ids = list({r.dept_id for r in rows_orm if r.dept_id})
+        dept_map: dict[str, str] = {}
+        if dept_ids:
+            for d in db.query(SysDept).filter(SysDept.id.in_(dept_ids), SysDept.del_flag == 0).all():
+                dept_map[d.id] = d.dept_name or ""
+        result_list = [
+            user_svc._row_to_dict(r, dept_name=dept_map.get(r.dept_id or "", "")) for r in rows_orm
+        ]
+        user_ids = [d["id"] for d in result_list if d.get("id")]
+        role_map: dict[str, list[str]] = {uid: [] for uid in user_ids}
+        role_name_by_id: dict[str, str] = {}
+        if user_ids:
+            for ur in (
+                db.query(SysUserRole)
+                .filter(
+                    SysUserRole.user_id.in_(user_ids),
+                    SysUserRole.del_flag == 0,
+                )
+                .all()
+            ):
+                role_map.setdefault(ur.user_id, []).append(ur.role_id)
+            role_ids = list({rid for ids in role_map.values() for rid in ids})
+            if role_ids:
+                for role in db.query(SysRole).filter(SysRole.id.in_(role_ids), SysRole.del_flag == 0).all():
+                    role_name_by_id[role.id] = role.role_name or role.id
+        for d in result_list:
+            names = [role_name_by_id.get(rid, rid) for rid in role_map.get(d["id"], [])]
+            d["roles"] = "、".join(names)
+        cols = filter_export_columns(USER_EXPORT_COLUMNS, body.columns)
+        date_str = datetime.now().strftime("%Y%m%d")
+        return export_rows_to_xlsx_response(
+            db,
+            tenant_no=tenant_no,
+            sheet_title="用户列表",
+            columns=cols,
+            rows=result_list,
+            filename_stem=f"用户列表_{date_str}",
+        )
+    except ExportValidationError as e:
+        return error_response(400, str(e))
+    except Exception as e:
+        return error_response(500, str(e))
+
+
 @router.get("/users/{user_id}")
 def get_user_by_id(
     user_id: str,