目录
[前端页面的script steup的代码参考](#前端页面的script steup的代码参考)
前期环境安装
安装 fastapi
bash
pip install fastapi
安装 uvicorn
bash
pip install uvicorn
安装 mysql包模块
bash
pip install mysql-connector-python
前端页面设计
页面效果

javascript
<template>
<div class="user-management">
<h1>用户管理</h1>
<!-- 搜索和添加按钮 -->
<div class="actions-bar">
<div class="search-box">
<input
type="text"
v-model="searchQuery"
placeholder="搜索用户名或邮箱..."
/>
</div>
<button @click="openAddDialog" class="add-btn">添加用户</button>
</div>
<!-- 提示消息 -->
<div v-if="error" class="error">{{ error }}</div>
<div v-if="successMessage" class="success">{{ successMessage }}</div>
<!-- 加载状态 -->
<div v-if="isLoading" class="loading">加载中...</div>
<!-- 用户列表 -->
<table v-else-if="filteredUsers.length > 0" class="user-table">
<thead>
<tr>
<th>用户名</th>
<th>年龄</th>
<th>邮箱</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="user in filteredUsers" :key="user.id">
<td>{{ user.username }}</td>
<td>{{ user.age }}</td>
<td>{{ user.email }}</td>
<td class="actions">
<button @click="openEditDialog(user.id)" class="edit-btn">编辑</button>
<button @click="confirmDelete(user.id)" class="delete-btn">删除</button>
</td>
</tr>
</tbody>
</table>
<!-- 空列表提示 -->
<p v-else class="empty-message">暂无用户数据</p>
<!-- 确认删除弹窗 -->
<div v-if="showDeleteConfirm" class="dialog-overlay">
<div class="dialog-content confirm-dialog">
<h3>确认删除</h3>
<p>确定要删除该用户吗?此操作不可撤销。</p>
<div class="dialog-footer">
<button @click="cancelDelete" class="cancel-btn">取消</button>
<button @click="deleteUser" class="delete-btn">删除</button>
</div>
</div>
</div>
<!-- 添加/编辑用户弹窗 -->
<UserFormDialog
:is-open="isDialogOpen"
:user-data="selectedUser"
:mode="dialogMode"
@close="closeDialog"
@submit="handleFormSubmit"
/>
</div>
</template>
<style scoped>
.user-management {
max-width: 800px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.actions-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 10px;
}
.search-box input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
width: 250px;
transition: border-color 0.3s;
}
.search-box input:focus {
outline: none;
border-color: #42b983;
}
.add-btn {
padding: 8px 16px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.add-btn:hover {
background-color: #3aa876;
}
.user-table {
width: 100%;
border-collapse: collapse;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.user-table th,
.user-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #eee;
}
.user-table th {
background-color: #f5f5f5;
font-weight: bold;
}
.user-table tr:hover {
background-color: #f9f9f9;
}
.actions {
display: flex;
gap: 10px;
}
.edit-btn {
padding: 5px 10px;
background-color: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.edit-btn:hover {
background-color: #2980b9;
}
.delete-btn {
padding: 5px 10px;
background-color: #e74c3c;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.delete-btn:hover {
background-color: #c0392b;
}
.error {
text-align: center;
color: #e74c3c;
margin-bottom: 16px;
padding: 10px;
background-color: #fdecea;
border-radius: 4px;
}
.success {
text-align: center;
color: #27ae60;
margin-bottom: 16px;
padding: 10px;
background-color: #eafaf1;
border-radius: 4px;
animation: fadeInOut 3s ease-in-out;
}
.loading {
text-align: center;
padding: 20px;
color: #7f8c8d;
}
.empty-message {
text-align: center;
padding: 20px;
color: #7f8c8d;
}
/* 确认删除弹窗样式 */
.dialog-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;
animation: fadeIn 0.3s ease-out;
}
.dialog-content {
background-color: white;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
overflow: hidden;
transform: translateY(-20px);
animation: slideUp 0.3s ease-out forwards;
}
.confirm-dialog {
width: 360px;
max-width: 90%;
padding: 24px;
text-align: center;
}
.confirm-dialog h3 {
font-size: 20px;
color: #333;
margin-top: 0;
margin-bottom: 16px;
font-weight: 600;
}
.confirm-dialog p {
margin-bottom: 24px;
color: #555;
font-size: 14px;
line-height: 1.5;
}
.dialog-footer {
display: flex;
justify-content: center;
gap: 12px;
margin-top: 16px;
}
.dialog-footer .cancel-btn {
padding: 10px 20px;
background-color: #f1f1f1;
color: #333;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.3s;
}
.dialog-footer .cancel-btn:hover {
background-color: #e0e0e0;
}
.dialog-footer .delete-btn {
padding: 10px 20px;
background-color: #e74c3c;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.3s, transform 0.2s;
}
.dialog-footer .delete-btn:hover {
background-color: #c0392b;
transform: translateY(-1px);
}
.dialog-footer .delete-btn:active {
transform: translateY(0);
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { transform: translateY(-20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes fadeInOut {
0% { opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { opacity: 0; }
}
/* 响应式调整 */
@media (max-width: 600px) {
.actions-bar {
flex-direction: column;
align-items: stretch;
}
.search-box input {
width: 100%;
margin-bottom: 10px;
}
.user-table {
display: block;
overflow-x: auto;
}
}
</style>
数据库表设计
users表
sql
create table users(
id int PRIMARY key auto_increment,
name varchar(50) not null,
age int
)
emails表
sql
create table emails(
id int PRIMARY key auto_increment,
user_id int,
email varchar(50) unique,
FOREIGN key (user_id) REFERENCES users(id)
)
后端设计
需要封装数据库调用和方法的py文件
以及fastapi的接口方法编写
Databases.py封装代码
python
class Database:
def __init__(self, host, database, user, password, port=3306):
self.host = host
self.database = database
self.user = user
self.password = password
self.port = port
def get_connection(self):
"""获取数据库连接"""
try:
connection = mysql.connector.connect(
host=self.host,
database=self.database,
user=self.user,
password=self.password,
port=self.port
)
if connection.is_connected():
logging.info("成功获取到 MySQL 数据库连接")
return connection
except Error as e:
logging.error(f"获取数据库连接时出错: {e}")
raise
def disconnect(self, connection):
"""关闭数据库连接"""
if connection and connection.is_connected():
connection.close()
logging.info("MySQL 连接已关闭")
def execute_query(self, query, params=None):
"""执行查询操作"""
try:
with self.get_connection() as connection:
cursor = connection.cursor(dictionary=True)
cursor.execute(query, params)
result = cursor.fetchall()
cursor.close()
return result
except Error as e:
logging.error(f"执行查询时出错: {e}")
raise
def execute_update(self, query, params=None):
"""执行更新操作(INSERT, UPDATE, DELETE)"""
try:
with self.get_connection() as connection:
cursor = connection.cursor()
cursor.execute(query, params)
connection.commit()
rowcount = cursor.rowcount
cursor.close()
return rowcount
except Error as e:
connection.rollback()
logging.error(f"执行更新时出错: {e}")
raise
后端api方法编写
在创建完fast的app对象后
记得编写以下内容,实现数据的全局共享
python
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
创建数据库对象
python
db = Database(
host='localhost',
database='数据库名称',
user='root',
password='password'
)
在创建完对象,之后再编写fastapi接口
编写获取数据库内表的全部数据的api接口
python
@app.get("/alldate")
async def get_all_date():
try:
with db.get_connection() as conn:
with conn.cursor(dictionary=True) as cur:
cur.execute("""
SELECT u.id, u.name, u.age, e.email
FROM users u
JOIN emails e ON u.id = e.user_id
""")
return cur.fetchall()
except Exception as e:
return {"error": str(e)}
添加用户adddate接口
python
@app.post("/adddate")
async def add_date(
name: str = Form(...),
age: int = Form(...),
email: str = Form(...)
):
try:
with db.get_connection() as conn:
with conn.cursor() as cur:
conn.start_transaction()
cur.execute(
"INSERT INTO users (name, age) VALUES (%s, %s)",
(name, age)
)
user_id = cur.lastrowid
cur.execute(
"INSERT INTO emails (email, user_id) VALUES (%s, %s)",
(email, user_id)
)
conn.commit()
return {"message": "添加成功"}
except Exception as e:
return {"message": f"添加失败: {str(e)}"}
注:传参的时候采用form传参,因为页面组件采用的是form表单,如果只是name:str这样子去传的话,实际运行会出现错误代码422传参传不过去的。
在上面这个接口,我们只要修改语句调用database.py内的方法,修改sql语句就能实现完整的增删改查,为了篇幅,这个要自己去写。
pinina数据共享的js编写
需要导入pinia模块
详细参考:介绍 | Pinia 中文文档
UserStore.js
javascript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
const users = ref([])
const loading = ref(false)
const error = ref(null)
const allUsers = computed(() => users.value)
const getUserById = (id) =>
users.value.find(u => u.id == id) || null
const fetchUsers = async () => {
loading.value = true
error.value = null
try {
const res = await fetch('http://127.0.0.1:8000/alldate')
const data = await res.json()
if (data.error) {
error.value = data.error
} else {
users.value = data.map(u => ({
id: u.id,
username: u.name,
age: u.age,
email: u.email
}))
}
} catch (err) {
error.value = err.message || err
} finally {
loading.value = false
}
}
const addUser = async ({ username, age, email }) => {
loading.value = true
error.value = null
try {
const res = await fetch('http://127.0.0.1:8000/adddate', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ name: username, age, email })
})
const result = await res.json()
if (result.message && result.message.includes('失败')) throw new Error(result.message)
await fetchUsers()
return result
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
const updateUser = async (id, { username, age, email }) => {
loading.value = true
error.value = null
try {
const res = await fetch('http://127.0.0.1:8000/updatedate', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ id, name: username, age, email })
})
const result = await res.json()
if (result.message && result.message.includes('失败')) throw new Error(result.message)
await fetchUsers()
return result
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
const deleteUser = async (id) => {
loading.value = true
error.value = null
try {
const res = await fetch('http://127.0.0.1:8000/deletedate', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ id })
})
const result = await res.json()
if (result.message && result.message.includes('失败')) throw new Error(result.message)
await fetchUsers()
return result
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
return {
users,
loading,
error,
allUsers,
fetchUsers,
addUser,
updateUser,
deleteUser,
getUserById
}
})
篇幅有限,下篇文章会详细讲解编写者一个模块。
前端页面的script steup的代码参考
javascript
import { ref, computed, watch, onMounted } from 'vue'
import { useUserStore } from '../stores/userStore'
import UserFormDialog from './UserFormDialog.vue'
/* ---------- 使用 Pinia ---------- */
const userStore = useUserStore()
/* ---------- 本地响应式变量 ---------- */
const searchQuery = ref('')
const isDialogOpen = ref(false)
const dialogMode = ref('add') // 'add' | 'edit'
const selectedUser = ref(null)
const showDeleteConfirm = ref(false)
const userIdToDelete = ref(null)
/* 用来在模板里展示的错误 / 成功提示 / 加载状态 */
const error = computed(() => userStore.error)
const isLoading = computed(() => userStore.loading)
const successMessage = ref('')
/* ---------- 搜索过滤 ---------- */
const filteredUsers = computed(() => {
const q = searchQuery.value.toLowerCase().trim()
if (!q) return userStore.allUsers
return userStore.allUsers.filter(u =>
u.username.toLowerCase().includes(q) ||
u.email.toLowerCase().includes(q)
)
})
/* ---------- 业务函数 ---------- */
const openAddDialog = () => {
dialogMode.value = 'add'
selectedUser.value = null
isDialogOpen.value = true
}
const openEditDialog = (id) => {
const user = userStore.getUserById(id)
if (user) {
dialogMode.value = 'edit'
selectedUser.value = { ...user }
isDialogOpen.value = true
}
}
const closeDialog = () => {
isDialogOpen.value = false
selectedUser.value = null
}
const handleFormSubmit = async (formData) => {
try {
if (dialogMode.value === 'add') {
await userStore.addUser(formData)
successMessage.value = '用户添加成功'
} else {
await userStore.updateUser(selectedUser.value.id, formData)
successMessage.value = '用户更新成功'
}
} catch (e) {
/* error 已在 store 里设置,无需手动赋值 */
}
}
const confirmDelete = (id) => {
userIdToDelete.value = id
showDeleteConfirm.value = true
}
const cancelDelete = () => {
showDeleteConfirm.value = false
userIdToDelete.value = null
}
const deleteUser = async () => {
if (userIdToDelete.value) {
try {
await userStore.deleteUser(userIdToDelete.value)
successMessage.value = '用户删除成功'
} catch {}
cancelDelete()
}
}
/* 3 秒后自动清空成功提示 */
const clearSuccess = () => {
setTimeout(() => (successMessage.value = ''), 3000)
}
watch(successMessage, () => successMessage.value && clearSuccess())
/* ---------- 初始化 ---------- */
onMounted(() => userStore.fetchUsers())
运行效果演示

