一、单文件简单实现
1.1 开发用户增删改查接口
main.py
from fastapi import FastAPI, Request, Query, HTTPException
from fastapi.responses import JSONResponse
from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseModel
from bson import ObjectId, errors as bson_errors
from fastapi.middleware.cors import CORSMiddleware
from typing import Optional, List
# 创建FastAPI应用实例
app = FastAPI(
title="用户管理API",
description="提供用户的增删改查和分页功能",
version="1.0.0"
)
# MongoDB 连接配置
MONGODB_URL = "mongodb://zhangdapeng:zhangdapeng520@localhost:27017"
DATABASE_NAME = "blogdb"
# 初始化MongoDB连接
client = AsyncIOMotorClient(MONGODB_URL)
db = client[DATABASE_NAME]
user_collection = db["users"]
# 配置CORS中间件
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 在生产环境中应该指定具体的域名
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 全局异常处理器
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""
全局异常处理器,捕获所有未处理的异常并返回统一的错误响应
"""
error_msg = str(exc)
if isinstance(exc, bson_errors.InvalidId):
return JSONResponse(
status_code=400,
content={"detail": "无效的ID格式"}
)
return JSONResponse(
status_code=500,
content={"detail": f"服务器内部错误: {error_msg}"}
)
# 用户数据模型
class User(BaseModel):
"""
用户数据模型,定义用户的基本信息字段
"""
name: str
age: int
gender: str
class Config:
json_schema_extra = {
"example": {
"name": "张三",
"age": 25,
"gender": "男"
}
}
# 用户分页响应模型
class UserResponse(BaseModel):
"""
用户列表分页响应模型
"""
total: int
users: List[dict]
# API接口实现
@app.post("/users/", response_model=dict, summary="创建新用户")
async def create_user(user: User):
"""
创建新用户
Args:
user: 用户信息对象
Returns:
dict: 包含用户ID和信息的字典
"""
result = await user_collection.insert_one(user.dict())
return {"_id": str(result.inserted_id), **user.dict()}
@app.get("/users/", response_model=UserResponse, summary="获取用户列表")
async def get_users(
page: int = Query(1, gt=0, description="页码"),
page_size: int = Query(10, gt=0, le=100, description="每页数量")
):
"""
获取用户列表,支持分页查询
Args:
page: 当前页码
page_size: 每页显示的数量
Returns:
UserResponse: 包含总数和用户列表的响应对象
"""
skip = (page - 1) * page_size
total = await user_collection.count_documents({})
users = await user_collection.find().skip(skip).limit(page_size).to_list(length=page_size)
# 转换ObjectId为字符串
for user in users:
user["_id"] = str(user["_id"])
return {"total": total, "users": users}
@app.put("/users/{user_id}", summary="更新用户信息")
async def update_user(user_id: str, user: User):
"""
更新指定用户的信息
Args:
user_id: 用户ID
user: 更新的用户信息
Returns:
dict: 更新结果信息
"""
result = await user_collection.update_one(
{"_id": ObjectId(user_id)},
{"$set": user.dict()}
)
if result.modified_count == 0:
raise HTTPException(status_code=404, detail="用户不存在")
return {"message": "用户更新成功"}
@app.delete("/users/{user_id}", summary="删除用户")
async def delete_user(user_id: str):
"""
删除指定的用户
Args:
user_id: 要删除的用户ID
Returns:
dict: 删除结果信息
"""
result = await user_collection.delete_one({"_id": ObjectId(user_id)})
if result.deleted_count == 0:
raise HTTPException(status_code=404, detail="用户不存在")
return {"message": "用户删除成功"}
@app.get("/users/{user_id}", summary="获取单个用户信息")
async def get_user(user_id: str):
"""
获取指定用户的详细信息
Args:
user_id: 用户ID
Returns:
dict: 用户信息
"""
user = await user_collection.find_one({"_id": ObjectId(user_id)})
if user is None:
raise HTTPException(status_code=404, detail="用户不存在")
user["_id"] = str(user["_id"])
return user
# 应用启动入口
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host='0.0.0.0', port=8080)
1.2 创建React项目
package.json
{
"name": "user_manager",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.6.0"
},
"devDependencies": {
"@types/react": "^18.3.21",
"@types/react-dom": "^18.3.7",
"@vitejs/plugin-react": "^4.4.1",
"globals": "^15.15.0",
"vite": "^6.3.5"
}
}
vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FastAPI+React用户管理系统</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
src/main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
src/App.jsx
import {Route, BrowserRouter, Routes} from "react-router-dom";
import UserList from "./page/UserList.jsx";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<UserList/>}/>
</Routes>
</BrowserRouter>
)
}
export default App;
src/page/UserList.jsx
function UserList() {
return (
<div>
<h1>用户列表</h1>
<p>这里是用户列表的内容。</p>
</div>
)
}
export default UserList;
运行:
pnpm i
pnpm dev
1.3 引入zdpreact.css
src/zdpreact.css
/* 表格基础样式 */
.table {
width: 100%; /* 表格宽度占满容器 */
border-collapse: collapse; /* 合并表格边框 */
margin-bottom: 20px; /* 底部外边距 */
}
/* 表格头部单元格样式 */
.table th {
background-color: #f2f2f2; /* 表头背景色 */
padding: 12px; /* 内边距 */
border: 1px solid #ddd; /* 边框样式 */
text-align: left; /* 文字左对齐 */
}
/* 表格数据单元格样式 */
.table td {
padding: 12px; /* 内边距 */
border: 1px solid #ddd; /* 边框样式 */
}
/* 分页容器样式 */
.pagination {
display: flex; /* 弹性布局 */
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
gap: 10px; /* 按钮之间的间距 */
margin-top: 20px; /* 顶部外边距 */
}
/* 分页按钮基础样式 */
.pagination-button {
padding: 8px 12px; /* 内边距 */
border: 1px solid #ddd; /* 边框样式 */
background-color: white; /* 背景色 */
cursor: pointer; /* 鼠标指针样式 */
border-radius: 4px; /* 圆角 */
transition: all 0.3s; /* 过渡效果 */
}
/* 分页按钮悬停效果 */
.pagination-button:hover:not(:disabled) {
background-color: #f0f0f0; /* 悬停背景色 */
border-color: #999; /* 悬停边框色 */
}
/* 当前页码按钮样式 */
.pagination-button.active {
background-color: #1890ff; /* 激活状态背景色 */
color: white; /* 激活状态文字颜色 */
border-color: #1890ff; /* 激活状态边框色 */
}
/* 禁用按钮样式 */
.pagination-button:disabled {
background-color: #f5f5f5; /* 禁用状态背景色 */
color: #d9d9d9; /* 禁用状态文字颜色 */
cursor: not-allowed; /* 禁用状态鼠标样式 */
border-color: #d9d9d9; /* 禁用状态边框色 */
}
/* 表格操作按钮样式 */
.table-button {
padding: 4px 8px; /* 内边距 */
border: 1px solid #ddd; /* 边框样式 */
background-color: white; /* 背景色 */
cursor: pointer; /* 鼠标指针样式 */
border-radius: 4px; /* 圆角 */
transition: all 0.3s; /* 过渡效果 */
}
.table-button:hover {
background-color: #f0f0f0; /* 悬停背景色 */
border-color: #999; /* 悬停边框色 */
}
/* 删除按钮特殊样式 */
.table-button.delete {
color: #ff4d4f; /* 文字颜色 */
border-color: #ff4d4f; /* 边框颜色 */
}
.table-button.delete:hover {
background-color: #fff1f0; /* 悬停背景色 */
border-color: #ff7875; /* 悬停边框色 */
}
/* 分页选择器样式 */
.pagination-select {
padding: 8px 12px; /* 内边距 */
border: 1px solid #ddd; /* 边框样式 */
border-radius: 4px; /* 圆角 */
background-color: white; /* 背景色 */
cursor: pointer; /* 鼠标指针样式 */
margin-left: 10px; /* 左边距 */
outline: none; /* 移除默认轮廓 */
}
.pagination-select:hover {
border-color: #999; /* 悬停边框色 */
}
.pagination-select:focus {
border-color: #1890ff; /* 聚焦边框色 */
}
/* 表格工具栏样式 */
.table-toolbar {
margin-bottom: 16px;
display: flex;
justify-content: flex-end;
}
/* 主要按钮样式 */
.table-button.primary {
background-color: #1890ff;
color: white;
border-color: #1890ff;
}
.table-button.primary:hover {
background-color: #40a9ff;
border-color: #40a9ff;
}
/* 表单样式 */
.form {
width: 100%;
max-width: 400px;
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.form-item {
margin-bottom: 16px;
}
.form-item label {
display: block;
margin-bottom: 8px;
}
/* 表单项输入框和选择框通用样式 */
.form-item input,
.form-item select {
width: 100%;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
line-height: 1.5;
transition: all 0.3s;
box-sizing: border-box;
background-color: #fff;
}
/* 输入框和选择框悬停样式 */
.form-item input:hover,
.form-item select:hover {
border-color: #40a9ff;
}
/* 输入框和选择框聚焦样式 */
.form-item input:focus,
.form-item select:focus {
border-color: #40a9ff;
outline: none;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
/* 选择框特殊样式 */
.form-item select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8.5L1.5 4h9z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 8px center;
padding-right: 30px;
}
/* 表单项标签样式 */
.form-item label {
display: block;
margin-bottom: 8px;
color: #000000d9;
font-size: 14px;
}
.form-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 20px;
}
/* 模态框遮罩层 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
/* 模态框容器 */
.modal {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
min-width: 400px;
}
/* 模态框头部 */
.modal-header {
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
}
.modal-header h3 {
margin: 0;
color: #000000d9;
font-weight: 500;
font-size: 16px;
line-height: 1.4;
}
/* 模态框内容区 */
.modal-content {
padding: 24px;
}
/* 模态框中的表单样式 */
.modal-form {
width: auto;
max-width: none;
padding: 0;
background: none;
box-shadow: none;
}
/* 模态框按钮容器 */
.modal-buttons {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
}
/* 表格容器样式 */
.table-container {
width: 100%;
overflow-x: auto;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 空状态样式 */
.table-empty-state,
.table-loading-state {
padding: 40px 0;
text-align: center;
color: #666;
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
}
.empty-text,
.loading-text {
font-size: 14px;
margin: 0;
color: #666;
}
/* 加载状态样式 */
.loading-spinner {
display: inline-block;
width: 30px;
height: 30px;
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 表格悬停效果 */
.table tr:hover {
background-color: #f5f5f5;
}
/* 确认删除弹窗样式 */
.confirm-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.65);
backdrop-filter: blur(4px);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
animation: confirmModalFadeIn 0.3s ease;
}
.confirm-modal {
background: white;
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
min-width: 400px;
padding: 24px;
transform-origin: center;
animation: confirmModalScaleIn 0.3s ease;
}
.confirm-modal-title {
margin: 0 0 16px 0;
color: #1f2937;
font-size: 20px;
font-weight: 600;
line-height: 1.4;
}
.confirm-modal-content {
margin: 0 0 24px 0;
color: #4b5563;
font-size: 16px;
line-height: 1.6;
}
.confirm-modal-buttons {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
.confirm-modal-button {
padding: 8px 20px;
border: none;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.confirm-modal-button-delete {
color: white;
background-color: #dc2626;
}
.confirm-modal-button-delete:hover {
background-color: #b91c1c;
transform: translateY(-1px);
}
.confirm-modal-button-cancel {
color: #4b5563;
background-color: #f3f4f6;
}
.confirm-modal-button-cancel:hover {
background-color: #e5e7eb;
transform: translateY(-1px);
}
@keyframes confirmModalFadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes confirmModalScaleIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
修改 src/App.jsx
import {Route, BrowserRouter, Routes} from "react-router-dom";
import "./zdpreact.css"
import UserList from "./page/UserList.jsx";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<UserList/>}/>
</Routes>
</BrowserRouter>
)
}
export default App;
1.4 引入zdpreq.js
src/api/zdpreq.js
/**
* HTTP请求客户端类
* 封装了基本的HTTP请求方法,提供统一的接口和错误处理
*/
class ZDPReq {
/**
* 构造函数
* @param {Object} config - 配置对象
* @param {string} config.baseURL - API基础URL,默认为http://localhost:8080
* @param {number} config.timeout - 请求超时时间(毫秒),默认为5000ms
*/
constructor(config = {}) {
this.config = {
baseURL: 'http://localhost:8080',
timeout: 5000,
...config
};
}
/**
* 创建超时Promise
* 用于实现请求超时控制,当请求超过指定时间后自动reject
* @param {number} timeout - 超时时间(毫秒)
* @returns {Promise} 超时Promise
* @private
*/
_createTimeoutPromise(timeout) {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('请求超时'));
}, timeout);
});
}
/**
* 处理HTTP响应
* 统一处理响应状态和错误,将响应转换为JSON格式
* @param {Response} response - fetch API的响应对象
* @returns {Promise<Object>} 解析后的JSON数据
* @throws {Error} 当响应状态不为2xx时抛出错误
* @private
*/
async _processResponse(response) {
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.detail || `请求失败: ${response.status}`);
}
return response.json();
}
/**
* 构建完整的请求URL
* 将baseURL、路径和查询参数组合成完整的URL
* @param {string} url - 请求路径
* @param {Object} params - URL查询参数对象
* @returns {string} 完整的URL字符串
* @private
*/
_createFullURL(url, params) {
const fullURL = new URL(this.config.baseURL + url);
if (params) {
Object.entries(params).forEach(([key, value]) => {
fullURL.searchParams.append(key, value);
});
}
return fullURL.toString();
}
/**
* 发送HTTP请求的核心方法
* 处理请求配置、超时控制和错误处理
* @param {string} method - HTTP方法(GET、POST、PUT、DELETE等)
* @param {string} url - 请求路径
* @param {Object} options - 请求配置选项
* @param {Object} [options.data] - 请求体数据
* @param {Object} [options.params] - URL查询参数
* @returns {Promise<Object>} 响应数据
* @throws {Error} 请求失败时抛出错误
* @private
*/
async _request(method, url, options = {}) {
const { data, params } = options;
const fullURL = this._createFullURL(url, params);
try {
const fetchOptions = {
method,
headers: {
'Content-Type': 'application/json'
}
};
if (data) {
fetchOptions.body = JSON.stringify(data);
}
const response = await Promise.race([
fetch(fullURL, fetchOptions),
this._createTimeoutPromise(this.config.timeout)
]);
return this._processResponse(response);
} catch (error) {
throw new Error(error.message || '请求失败');
}
}
/**
* 发送GET请求
* @param {string} url - 请求路径
* @param {Object} [params] - URL查询参数
* @returns {Promise<Object>} 响应数据
*/
async get(url, params) {
return this._request('GET', url, { params });
}
/**
* 发送POST请求
* @param {string} url - 请求路径
* @param {Object} data - 请求体数据
* @returns {Promise<Object>} 响应数据
*/
async post(url, data) {
return this._request('POST', url, { data });
}
/**
* 发送PUT请求
* @param {string} url - 请求路径
* @param {Object} data - 请求体数据
* @returns {Promise<Object>} 响应数据
*/
async put(url, data) {
return this._request('PUT', url, { data });
}
/**
* 发送DELETE请求
* @param {string} url - 请求路径
* @returns {Promise<Object>} 响应数据
*/
async delete(url) {
return this._request('DELETE', url);
}
}
// 创建并导出HTTP客户端实例
export const zdpreq = new ZDPReq();
1.5 封装用户API接口请求
src/api/userApi.js
import { zdpreq } from './zdpreq';
// 用户API对象
export const userApi = {
// 获取用户列表
getPageUser: async (page = 1, pageSize = 10) => {
try {
return await zdpreq.get('/users/', {
page,
page_size: pageSize
});
} catch (error) {
throw new Error(error.message || '获取用户列表失败');
}
},
// 获取单个用户信息
getUser: async (userId) => {
try {
return await zdpreq.get(`/users/${userId}`);
} catch (error) {
throw new Error(error.message || '获取用户信息失败');
}
},
// 创建新用户
addUser: async (userData) => {
try {
return await zdpreq.post('/users/', userData);
} catch (error) {
throw new Error(error.message || '创建用户失败');
}
},
// 更新用户信息
updateUser: async (userId, userData) => {
try {
return await zdpreq.put(`/users/${userId}`, userData);
} catch (error) {
throw new Error(error.message || '更新用户失败');
}
},
// 删除用户
deleteUser: async (userId) => {
try {
return await zdpreq.delete(`/users/${userId}`);
} catch (error) {
throw new Error(error.message || '删除用户失败');
}
}
};
1.6 引入表格组件
src/zdpreact/component/Table.jsx
import React from 'react';
/**
* 通用表格组件
* @zdpreact
* @description 这是一个可复用的表格组件,支持自定义列配置和数据渲染
*
* @param {Object[]} columns - 表格列配置数组
* @param {string} columns[].key - 列的唯一标识,用于数据映射
* @param {string} columns[].title - 列的标题文本
* @param {function} [columns[].render] - 可选的自定义渲染函数,用于自定义单元格内容
*
* @param {Object[]} data - 表格数据数组,每个对象代表一行数据
* @param {boolean} loading - 加载状态标志,控制是否显示加载动画
*/
function Table({ columns, data, loading }) {
/**
* 渲染空数据状态的UI组件
* @returns {JSX.Element} 返回空状态的JSX元素
*/
const renderEmpty = () => (
<div className="table-empty-state">
<div className="empty-icon">📭</div>
<p className="empty-text">暂无数据</p>
</div>
);
/**
* 渲染加载状态的UI组件
* @returns {JSX.Element} 返回加载状态的JSX元素
*/
const renderLoading = () => (
<div className="table-loading-state">
<div className="loading-spinner"></div>
<p className="loading-text">加载中...</p>
</div>
);
return (
<div className="table-container">
<table className="table">
{/* 表头部分 */}
<thead>
<tr>
{columns.map(column => (
<th key={column.key}>{column.title}</th>
))}
</tr>
</thead>
{/* 表格主体部分 */}
<tbody>
{loading ? (
// 加载状态显示加载动画
<tr>
<td colSpan={columns.length}>
{renderLoading()}
</td>
</tr>
) : data.length > 0 ? (
// 有数据时渲染数据行
data.map((row, rowIndex) => (
<tr key={row.id || rowIndex}>
{columns.map(column => (
<td key={column.key}>
{/* 如果列配置中有render函数则使用自定义渲染,否则直接显示数据 */}
{column.render ? column.render(row) : row[column.key]}
</td>
))}
</tr>
))
) : (
// 无数据时显示空状态
<tr>
<td colSpan={columns.length}>
{renderEmpty()}
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}
export default Table;
1.7 引入分页组件
src/zdpreact/component/Pagination.jsx
import React from 'react';
// 分页组件
function Pagination({ currentPage, totalPages, onPageChange, pageSize, onPageSizeChange }) {
// 定义可选的每页显示数量
const pageSizeOptions = [2, 5, 10, 20, 50, 100];
return (
<div className="pagination">
{/* 上一页按钮 */}
<button
className="pagination-button"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
>
上一页
</button>
{/* 页码按钮 */}
{Array.from({ length: totalPages }, (_, index) => (
<button
key={index + 1}
className={`pagination-button ${currentPage === index + 1 ? 'active' : ''}`}
onClick={() => onPageChange(index + 1)}
>
{index + 1}
</button>
))}
{/* 下一页按钮 */}
<button
className="pagination-button"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
下一页
</button>
{/* 每页显示数量选择器 */}
<select
className="pagination-select"
value={pageSize}
onChange={(e) => onPageSizeChange(Number(e.target.value))}
>
{pageSizeOptions.map(size => (
<option key={size} value={size}>
{size} 条/页
</option>
))}
</select>
</div>
);
}
export default Pagination;
1.8 引入表单模态框组件
src/zdpreact/component/FormModal.jsx
import React from 'react';
/**
* 表单模态框组件
* @param {Object} props 组件属性
* @param {boolean} props.visible 是否显示模态框
* @param {string} props.title 模态框标题
* @param {Array} props.fields 表单字段配置
* @param {Object} props.initialValues 表单初始值
* @param {function} props.onSubmit 表单提交回调
* @param {function} props.onCancel 取消按钮回调
*/
function FormModal({ visible, title, fields, initialValues = {}, onSubmit, onCancel }) {
if (!visible) return null;
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = {};
fields.forEach(field => {
const value = formData.get(field.name);
data[field.name] = field.type === 'number' ? parseInt(value) : value;
});
onSubmit(data);
};
return (
<div className="modal-overlay">
<div className="modal">
<div className="modal-header">
<h3>{title}</h3>
</div>
<div className="modal-content">
<form className="form modal-form" onSubmit={handleSubmit}>
{fields.map(field => (
<div className="form-item" key={field.name}>
<label>{field.label}:</label>
{field.type === 'select' ? (
<select
name={field.name}
required={field.required}
defaultValue={initialValues[field.name] || ''}
>
<option value="">{field.placeholder || '请选择'}</option>
{field.options.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
) : (
<input
type={field.type}
name={field.name}
required={field.required}
placeholder={field.placeholder}
defaultValue={initialValues[field.name] || ''}
/>
)}
</div>
))}
<div className="modal-buttons">
<button type="submit" className="table-button primary">
确认
</button>
<button
type="button"
className="table-button"
onClick={onCancel}
>
取消
</button>
</div>
</form>
</div>
</div>
</div>
);
}
export default FormModal;
1.9 引入确认模态框组件
src/zdpreact/component/ConfirmModal.jsx
import React from 'react';
/**
* 确认模态框组件
* @param {Object} props 组件属性
* @param {boolean} props.visible 是否显示模态框
* @param {string} props.title 模态框标题
* @param {string} props.content 模态框内容
* @param {function} props.onConfirm 确认按钮回调
* @param {function} props.onCancel 取消按钮回调
*/
function ConfirmModal({ visible, title, content, onConfirm, onCancel }) {
if (!visible) return null;
return (
<div className="confirm-modal-overlay">
<div className="confirm-modal">
<h3 className="confirm-modal-title">{title}</h3>
<p className="confirm-modal-content">{content}</p>
<div className="confirm-modal-buttons">
<button
className="confirm-modal-button confirm-modal-button-delete"
onClick={onConfirm}
>
确认
</button>
<button
className="confirm-modal-button confirm-modal-button-cancel"
onClick={onCancel}
>
取消
</button>
</div>
</div>
</div>
);
}
export default ConfirmModal;
1.10 实现用户增删改查功能
src/page/UserList.jsx
import { useState, useEffect } from 'react';
import Pagination from '../zdpreact/component/Pagination.jsx';
import Table from '../zdpreact/component/Table.jsx';
import ConfirmModal from '../zdpreact/component/ConfirmModal.jsx';
import FormModal from '../zdpreact/component/FormModal.jsx';
import { userApi } from '../api/userApi.js';
/**
* 用户列表组件
* 实现用户的增删改查功能,包含分页、搜索、表单验证等功能
*/
function UserList() {
// ================ 状态管理 ================
// 用户数据相关状态
const [users, setUsers] = useState([]); // 用户列表数据
const [loading, setLoading] = useState(false); // 加载状态
const [error, setError] = useState(null); // 错误信息
// 分页相关状态
const [currentPage, setCurrentPage] = useState(1); // 当前页码
const [itemsPerPage, setItemsPerPage] = useState(10); // 每页条数
const [totalItems, setTotalItems] = useState(0); // 总记录数
// 模态框状态管理
const [deleteConfirm, setDeleteConfirm] = useState({
visible: false,
record: null
});
const [addUserModal, setAddUserModal] = useState({
visible: false
});
const [editUserModal, setEditUserModal] = useState({
visible: false,
record: null
});
// ================ 数据加载 ================
/**
* 加载用户列表数据
* 包含错误处理和加载状态管理
*/
const loadUsers = async () => {
try {
setLoading(true);
setError(null);
const response = await userApi.getPageUser(currentPage, itemsPerPage);
setUsers(response.users);
setTotalItems(response.total);
} catch (err) {
setError('加载用户数据失败:' + (err.message || '未知错误'));
console.error('加载用户数据失败:', err);
} finally {
setLoading(false);
}
};
// 监听分页参数变化,重新加载数据
useEffect(() => {
loadUsers();
}, [currentPage, itemsPerPage]);
// ================ 事件处理 ================
/**
* 处理每页显示数量变化
* @param {number} newSize - 新的每页显示数量
*/
const handlePageSizeChange = (newSize) => {
setItemsPerPage(newSize);
setCurrentPage(1); // 切换每页数量时重置为第一页
};
/**
* 处理新增用户按钮点击
*/
const showAddUserModal = () => {
setAddUserModal({ visible: true });
};
/**
* 处理编辑按钮点击
* @param {Object} record - 当前行用户数据
*/
const handleEdit = (record) => {
setEditUserModal({
visible: true,
record: { ...record } // 创建数据副本,避免直接修改原数据
});
};
/**
* 显示删除确认框
* @param {Object} record - 当前行用户数据
*/
const showDeleteConfirm = (record) => {
setDeleteConfirm({
visible: true,
record
});
};
// ================ 模态框操作处理 ================
/**
* 处理删除确认
*/
const handleDeleteConfirm = async () => {
try {
setLoading(true);
await userApi.deleteUser(deleteConfirm.record._id);
await loadUsers();
setDeleteConfirm({
visible: false,
record: null
});
} catch (err) {
setError('删除用户失败:' + (err.message || '未知错误'));
console.error('删除用户失败:', err);
} finally {
setLoading(false);
}
};
/**
* 处理删除取消
*/
const handleDeleteCancel = () => {
setDeleteConfirm({
visible: false,
record: null
});
};
/**
* 处理新增用户确认
* @param {Object} formData - 表单数据
*/
const handleAddUserConfirm = async (formData) => {
try {
setLoading(true);
await userApi.addUser(formData);
await loadUsers();
setAddUserModal({ visible: false });
} catch (err) {
setError('新增用户失败:' + (err.message || '未知错误'));
console.error('新增用户失败:', err);
} finally {
setLoading(false);
}
};
/**
* 处理新增用户取消
*/
const handleAddUserCancel = () => {
setAddUserModal({ visible: false });
};
/**
* 处理编辑用户确认
* @param {Object} formData - 表单数据
*/
const handleEditUserConfirm = async (formData) => {
try {
setLoading(true);
await userApi.updateUser(editUserModal.record._id, formData);
await loadUsers();
setEditUserModal({
visible: false,
record: null
});
} catch (err) {
setError('更新用户失败:' + (err.message || '未知错误'));
console.error('更新用户失败:', err);
} finally {
setLoading(false);
}
};
/**
* 处理编辑用户取消
*/
const handleEditUserCancel = () => {
setEditUserModal({
visible: false,
record: null
});
};
// ================ 配置项 ================
// 表格列定义
const columns = [
{ key: 'name', title: '姓名' },
{ key: 'age', title: '年龄' },
{ key: 'gender', title: '性别' },
{
key: 'action',
title: '操作',
render: (record) => (
<div style={{ display: 'flex', gap: '8px' }}>
<button
className="table-button"
onClick={() => handleEdit(record)}
disabled={loading}
>
编辑
</button>
<button
className="table-button delete"
onClick={() => showDeleteConfirm(record)}
disabled={loading}
>
删除
</button>
</div>
)
}
];
// 用户表单字段配置
const userFields = [
{
name: 'name',
label: '姓名',
type: 'text',
required: true
},
{
name: 'age',
label: '年龄',
type: 'number',
required: true
},
{
name: 'gender',
label: '性别',
type: 'select',
required: true,
options: [
{ value: '男', label: '男' },
{ value: '女', label: '女' }
]
}
];
// ================ 渲染 ================
return (
<div className="App">
<header className="App-header">
<h1>用户列表</h1>
{/* 错误信息显示 */}
{error && (
<div className="error-message" style={{ color: 'red', margin: '10px 0' }}>
{error}
</div>
)}
{/* 操作栏 */}
<div className="table-toolbar">
<button
className="table-button primary"
onClick={showAddUserModal}
disabled={loading}
>
新增用户
</button>
</div>
{/* 用户列表表格 */}
<Table
columns={columns}
data={users}
loading={loading}
/>
{/* 分页组件 */}
<Pagination
currentPage={currentPage}
totalPages={Math.ceil(totalItems / itemsPerPage)}
onPageChange={setCurrentPage}
pageSize={itemsPerPage}
onPageSizeChange={handlePageSizeChange}
/>
{/* 删除确认框 */}
<ConfirmModal
visible={deleteConfirm.visible}
title="确认删除"
content={`确定要删除用户 "${deleteConfirm.record?.name}" 吗?`}
onConfirm={handleDeleteConfirm}
onCancel={handleDeleteCancel}
/>
{/* 新增用户表单 */}
<FormModal
visible={addUserModal.visible}
title="新增用户"
fields={userFields}
onSubmit={handleAddUserConfirm}
onCancel={handleAddUserCancel}
/>
{/* 编辑用户表单 */}
<FormModal
visible={editUserModal.visible}
title="编辑用户"
fields={userFields}
initialValues={editUserModal.record}
onSubmit={handleEditUserConfirm}
onCancel={handleEditUserCancel}
/>
</header>
</div>
);
}
export default UserList;
二、经典三层架构
2.1 MongoDB数据库依赖
app/dependency/database.py
from typing import AsyncGenerator
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
from fastapi import Depends
# MongoDB配置
MONGO_URL = "mongodb://zhangdapeng:zhangdapeng520@localhost:27017/"
DATABASE_NAME = "users_db"
# 创建MongoDB客户端
client = AsyncIOMotorClient(MONGO_URL)
async def get_database() -> AsyncGenerator[AsyncIOMotorDatabase, None]:
"""
获取数据库连接的依赖函数
"""
try:
db = client[DATABASE_NAME]
yield db
finally:
# 在这里可以添加清理代码如果需要
pass
# 获取用户集合的依赖函数
async def get_user_collection():
"""
获取用户集合的依赖函数
"""
db = client[DATABASE_NAME]
return db["users"]
2.2 跨域中间件
app/middleware/cors_middleware.py
from fastapi.middleware.cors import CORSMiddleware
def setup_cors_middleware(app):
"""配置CORS中间件"""
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
2.3 全局异常处理
app/middleware/error_handler.py
from fastapi import Request
from fastapi.responses import JSONResponse
from bson import errors as bson_errors
def setup_error_handler(app):
"""配置全局异常处理器"""
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
error_msg = str(exc)
if isinstance(exc, bson_errors.InvalidId):
return JSONResponse(
status_code=400,
content={"message": "Invalid ObjectId"},
)
return JSONResponse(
status_code=500,
content={"message": f"服务器内部错误: {error_msg}"},
)
2.4 初始化所有中间件
app/middleware/__init__.py
from .cors_middleware import setup_cors_middleware
from .error_handler import setup_error_handler
def setup_middlewares(app):
"""初始化所有中间件"""
setup_cors_middleware(app)
setup_error_handler(app)
2.5 用户请求参数
app/schema/user_model.py
from pydantic import BaseModel
from typing import List
class User(BaseModel):
name: str
age: int
gender: str
class Config:
json_schema_extra = {
"example": {
"name": "张三",
"age": 30,
"gender": "男"
}
}
class UserResponse(BaseModel):
total: int
users: List[dict]
2.6 用户dao层
app/dao/user_dao.py
from bson import ObjectId
from ..dependency.database import get_user_collection
class UserDAO:
def __init__(self):
self.collection = None
async def initialize(self):
"""初始化数据库集合"""
self.collection = await get_user_collection()
async def create_user(self, user_dict: dict) -> str:
"""创建用户"""
await self.initialize()
result = await self.collection.insert_one(user_dict)
return str(result.inserted_id)
async def get_users(self, skip: int, limit: int) -> tuple[list, int]:
"""获取用户列表"""
await self.initialize()
users = await self.collection.find().skip(skip).limit(limit).to_list(length=limit)
total = await self.collection.count_documents({})
for user in users:
user["_id"] = str(user["_id"])
return users, total
async def update_user(self, user_id: str, user_dict: dict) -> bool:
"""更新用户"""
await self.initialize()
result = await self.collection.update_one(
{"_id": ObjectId(user_id)},
{"$set": user_dict}
)
return result.modified_count > 0
async def delete_user(self, user_id: str) -> bool:
"""删除用户"""
await self.initialize()
result = await self.collection.delete_one({"_id": ObjectId(user_id)})
return result.deleted_count > 0
async def get_user(self, user_id: str) -> dict:
"""获取单个用户"""
await self.initialize()
user = await self.collection.find_one({"_id": ObjectId(user_id)})
if user:
user["_id"] = str(user["_id"])
return user
2.7 用户service层
app/service/user_service.py
from fastapi import HTTPException
from ..dao.user_dao import UserDAO
from ..schema.user_model import User
class UserService:
def __init__(self):
self.user_dao = UserDAO()
async def create_user(self, user: User) -> dict:
"""
创建用户服务
"""
user_dict = user.dict()
user_id = await self.user_dao.create_user(user_dict)
return {"message": "用户创建成功", "id": user_id}
async def get_users(self, skip: int, page_size: int) -> dict:
"""
获取用户列表服务
"""
users, total = await self.user_dao.get_users(skip, page_size)
return {"total": total, "users": users}
async def update_user(self, user_id: str, user: User) -> dict:
"""
更新用户服务
"""
user_dict = user.dict()
if not await self.user_dao.update_user(user_id, user_dict):
raise HTTPException(status_code=404, detail="用户不存在")
return {"message": "用户更新成功"}
async def delete_user(self, user_id: str) -> dict:
"""
删除用户服务
"""
if not await self.user_dao.delete_user(user_id):
raise HTTPException(status_code=404, detail="用户不存在")
return {"message": "用户删除成功"}
async def get_user(self, user_id: str) -> User:
"""
获取单个用户服务
"""
user = await self.user_dao.get_user(user_id)
if user is None:
raise HTTPException(status_code=404, detail="用户不存在")
return User(**user)
2.8 用户controller层
app/controller/user_controller.py
from fastapi import APIRouter, Query
from ..schema.user_model import User, UserResponse
from ..service.user_service import UserService
router = APIRouter(tags=["用户管理"])
user_service = UserService()
@router.post("/users/", response_model=dict, summary="创建新用户")
async def create_user(user: User):
return await user_service.create_user(user)
@router.get("/users/", response_model=UserResponse, summary="获取用户列表")
async def get_users(
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1)):
skip = (page - 1) * page_size
return await user_service.get_users(skip, page_size)
@router.put("/users/{id}", response_model=dict, summary="更新用户信息")
async def update_user(id: str, user: User):
return await user_service.update_user(id, user)
@router.delete("/users/{id}", response_model=dict, summary="删除用户")
async def delete_user(id: str):
return await user_service.delete_user(id)
@router.get("/users/{id}", response_model=User, summary="获取单个用户信息")
async def get_user(id: str):
return await user_service.get_user(id)
2.9 入口程序
main.py
from fastapi import FastAPI
from app.controller.user_controller import router as user_router
from app.middleware import setup_middlewares
# 创建app
app = FastAPI(
title="用户管理API",
description="提供用户增删改查和分页查询功能",
version="1.0.0",
)
# 初始化中间件
setup_middlewares(app)
# 注册路由
app.include_router(user_router, tags=["用户管理"])
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8080)
总结
源滚滚编程提供全套的PDF文档,配套源代码,录播课,私教课和直播课,关注并私信我咨询获取。