用户管理系统后台管理界面
用户管理系统后台管理界面主要功能:
- 查看用户列表:展示所有用户的基本信息
- 搜索用户:根据条件筛选用户
- 添加新用户:创建新的用户账户
- 编辑用户信息:修改现有用户的数据
- 删除用户:移除不需要的用户
- 批量操作:导入/导出用户数据
1.2 技术栈介绍
Vue 3
// Vue 3 的特点
- 组合式API (Composition API)
- 更好的性能
- 更小的包体积
- 更好的 TypeScript 支持
Element Plus
// UI 组件库,提供:
- 表格 (el-table)
- 表单 (el-form)
- 按钮 (el-button)
- 对话框 (el-dialog)
- 分页 (el-pagination)
// 等常用组件
第二部分:项目结构分析
2.1 文件组织
src/views/UserManagement.vue
├── <template> # 页面结构
├── <script setup> # 逻辑代码
└── <style> # 样式定义
2.2 组件架构
用户管理页面
├── 搜索和操作栏
│ ├── 搜索框
│ ├── 搜索/重置按钮
│ └── 新增/导出/导入按钮
├── 数据表格
│ ├── 用户信息列
│ ├── 状态开关
│ └── 操作按钮
├── 分页组件
└── 新增/编辑对话框├── 表单字段├── 验证规则└── 提交/取消按钮
Vue 3 基础语法详解
3.1 Composition API 基础
响应式数据定义
import { ref, reactive } from "vue";// ref:用于基本类型数据
const loading = ref(false);
const dialogVisible = ref(false);// reactive:用于对象类型数据
const searchForm = reactive({username: "",
});const userForm = reactive({id: null,username: "",email: "",// ...其他字段
});
为什么要区分 ref 和 reactive?
ref
:包装基本类型(string, number, boolean),通过.value
访问reactive
:直接包装对象,可以直接访问属性
模板引用
const userFormRef = ref();// 在模板中使用
<el-form ref="userFormRef"><!-- 表单内容 -->
</el-form>// 在代码中调用表单方法
const valid = await userFormRef.value.validate();
3.2 生命周期钩子
import { onMounted } from "vue";onMounted(() => {getList(); // 组件挂载后获取数据
});
页面结构详细分析
4.1 搜索和操作栏
<el-card class="search-card"><el-row :gutter="20"><!-- 搜索区域 --><el-col :span="6"><el-inputv-model="searchForm.username"placeholder="请输入用户名"clearable@clear="handleSearch"@keyup.enter="handleSearch"><template #prefix><el-icon><Search /></el-icon></template></el-input></el-col><!-- 搜索按钮 --><el-col :span="6"><el-button type="primary" @click="handleSearch">搜索</el-button><el-button @click="resetSearch">重置</el-button></el-col><!-- 操作按钮 --><el-col :span="12" style="text-align: right"><el-button type="primary" @click="showAddDialog"><el-icon><Plus /></el-icon>新增用户</el-button><!-- 导出/导入按钮 --></el-col></el-row>
</el-card>
关键语法解释:
v-model="searchForm.username"
:双向数据绑定@click="handleSearch"
:事件监听@keyup.enter
:回车键事件clearable
:显示清空按钮:span="6"
:栅格布局,占6列(总共24列)
4.2 数据表格
<el-table:data="tableData"v-loading="loading"stripestyle="width: 100%"
><!-- 基础列 --><el-table-column prop="id" label="ID" width="80" /><el-table-column prop="username" label="用户名" /><!-- 自定义列:分数标签 --><el-table-column prop="score" label="分数" width="80"><template #default="{ row }"><el-tag :type="getScoreType(row.score)">{{ row.score }}</el-tag></template></el-table-column><!-- 自定义列:状态开关 --><el-table-column prop="status" label="状态" width="100"><template #default="{ row }"><el-switchv-model="row.status":active-value="1":inactive-value="0"@change="handleStatusChange(row)"/></template></el-table-column><!-- 操作列 --><el-table-column label="操作" width="200" fixed="right"><template #default="{ row }"><el-button type="primary" size="small" @click="showEditDialog(row)">编辑</el-button><el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button></template></el-table-column>
</el-table>
关键概念:
:data="tableData"
:表格数据绑定v-loading="loading"
:加载状态template #default="{ row }"
:插槽,自定义列内容{ row }
:解构赋值,获取当前行数据
核心功能实现详解
5.1 数据获取功能
基础版本
const getList = async () => {const res = await getUserList();tableData.value = res.data.list;
};
完整版本(带搜索、分页、错误处理)
const getList = async () => {// 1. 设置加载状态loading.value = true;try {// 2. 准备请求参数const params = {pageNum: pagination.pageNum, // 当前页码pageSize: pagination.pageSize, // 每页条数};// 3. 添加搜索条件(如果有)if (searchForm.username) {params.username = searchForm.username;}// 4. 发送请求const res = await getUserList(params);// 5. 更新数据tableData.value = res.data.list;pagination.total = res.data.total;} catch (error) {// 6. 错误处理ElMessage.error("获取用户列表失败");} finally {// 7. 清除加载状态loading.value = false;}
};
async/await
:异步处理try-catch-finally
:错误处理模式- 参数的动态构建
- 加载状态的管理
5.2 搜索功能实现
// 搜索处理
const handleSearch = () => {pagination.pageNum = 1; // 重置到第一页getList(); // 重新获取数据
};// 重置搜索
const resetSearch = () => {searchForm.username = ""; // 清空搜索条件handleSearch(); // 执行搜索
};
搜索时可能数据总数发生变化,当前页码可能超出范围,所以要重置到第一页。
5.3 分页功能实现
// 分页数据
const pagination = reactive({pageNum: 1, // 当前页码pageSize: 10, // 每页条数total: 0, // 总条数
});// 每页条数改变
const handleSizeChange = () => {pagination.pageNum = 1; // 重置页码getList(); // 重新获取数据
};// 页码改变
const handleCurrentChange = () => {getList(); // 获取新页面数据
};
5.4 表单验证系统
验证规则定义
const rules = {username: [{ required: true, message: "请输入用户名", trigger: "blur" },{ min: 3, max: 20, message: "长度在 3 到 20 个字符", trigger: "blur" },],email: [{ required: true, message: "请输入邮箱", trigger: "blur" },{ type: "email", message: "请输入正确的邮箱地址", trigger: "blur" },],phone: [{pattern: /^1[3-9]\d{9}$/,message: "请输入正确的手机号",trigger: "blur",},],
};
表单提交验证
const submitForm = async () => {// 1. 表单验证const valid = await userFormRef.value.validate();if (!valid) return; // 验证失败,直接返回try {// 2. 根据是否编辑选择APIif (isEdit.value) {await updateUser(userForm.id, userForm);ElMessage.success("更新成功");} else {await createUser(userForm);ElMessage.success("创建成功");}// 3. 关闭对话框,刷新列表dialogVisible.value = false;getList();} catch (error) {ElMessage.error(error.message || "操作失败");}
};
6.1 状态切换功能
const handleStatusChange = async (row) => {const originalStatus = row.status; // 保存原始状态try {// 发送更新请求await updateUser(row.id, { status: row.status });ElMessage.success("状态更新成功");} catch (error) {// 失败时回滚状态row.status = originalStatus === 1 ? 0 : 1;ElMessage.error("状态更新失败");}
};
乐观更新模式:
- 先更新界面(用户立即看到变化)
- 发送服务器请求
- 如果失败,回滚界面状态
6.2 删除确认功能
const handleDelete = async (row) => {try {// 1. 确认对话框await ElMessageBox.confirm(`确定要删除用户 ${row.username} 吗?`,"删除确认",{confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",});// 2. 执行删除await deleteUser(row.id);ElMessage.success("删除成功");// 3. 刷新列表getList();} catch (error) {// 4. 错误处理(区分取消和真正的错误)if (error !== "cancel") {ElMessage.error("删除失败");}}
};
6.3 CSV导出功能
const handleExport = async () => {try {// 1. 获取导出数据const res = await exportUsersCsv();// 2. 创建Blob对象const blob = new Blob([res], { type: "text/csv;charset=utf-8;" });// 3. 创建下载链接const url = window.URL.createObjectURL(blob);const link = document.createElement("a");link.href = url;link.download = `用户数据_${new Date().getTime()}.csv`;// 4. 触发下载document.body.appendChild(link);link.click();// 5. 清理资源setTimeout(() => {document.body.removeChild(link);window.URL.revokeObjectURL(url);}, 100);ElMessage.success("导出成功");} catch (error) {ElMessage.error("导出失败");}
};
文件下载原理:
- 服务器返回文件数据
- 创建Blob对象(二进制数据)
- 生成临时URL
- 创建a标签触发下载
- 清理临时资源
6.4 CSV导入功能
const handleImport = async (file) => {// 1. 创建表单数据const formData = new FormData();formData.append("file", file);try {// 2. 上传文件const res = await importUsersCsv(formData);const { successCount, errorCount, errors } = res.data;// 3. 根据结果显示不同消息if (errorCount > 0) {ElMessage.warning(`导入完成:成功 ${successCount} 条,失败 ${errorCount} 条`);console.error("导入错误:", errors);} else {ElMessage.success(`导入成功:共 ${successCount} 条数据`);}// 4. 刷新列表getList();} catch (error) {ElMessage.error("导入失败");}// 5. 阻止默认上传行为return false;
};
工具函数
7.1 工具函数
// 根据分数返回标签类型
const getScoreType = (score) => {if (score >= 90) return "success"; // 绿色if (score >= 60) return "warning"; // 橙色return "danger"; // 红色
};// 格式化日期显示
const formatDate = (dateStr) => {if (!dateStr) return "";const date = new Date(dateStr);return date.toLocaleString("zh-CN");
};
7.2 表单重置
const resetForm = () => {// 1. 重置表单验证状态userFormRef.value?.resetFields();// 2. 重置表单数据Object.assign(userForm, {id: null,username: "",password: "",email: "",phone: "",score: 0,status: 1,});
};
7.3 对话框管理
// 新增用户
const showAddDialog = () => {isEdit.value = false;dialogTitle.value = "新增用户";dialogVisible.value = true;
};// 编辑用户
const showEditDialog = (row) => {isEdit.value = true;dialogTitle.value = "编辑用户";Object.assign(userForm, row); // 复制数据到表单dialogVisible.value = true;
};
样式
8.1 CSS样式
.user-management {.search-card {margin-bottom: 20px; // 搜索卡片间距}.table-card {.el-table {margin-bottom: 20px; // 表格底部间距}}
}
-
加载状态:
v-loading
让用户知道数据正在加载 -
即时反馈:操作后立即显示成功/失败消息
-
确认操作:删除等危险操作需要确认
-
表单验证:实时验证用户输入
-
状态回滚:操作失败时恢复原始状态
-
组合式API:更好的代码组织和复用
-
响应式数据:自动更新界面
-
组件化开发:模块化、可维护
-
完善的错误处理:提升用户体验
-
统一的数据流:清晰的数据管理
<!-- src/views/UserManagement.vue -->
<template><div class="user-management"><!-- 搜索和操作栏 --><el-card class="search-card"><el-row :gutter="20"><el-col :span="6"><el-inputv-model="searchForm.username"placeholder="请输入用户名"clearable@clear="handleSearch"@keyup.enter="handleSearch"><template #prefix><el-icon><Search /></el-icon></template></el-input></el-col><el-col :span="6"><el-button type="primary" @click="handleSearch">搜索</el-button><el-button @click="resetSearch">重置</el-button></el-col><el-col :span="12" style="text-align: right"><el-button type="primary" @click="showAddDialog"><el-icon><Plus /></el-icon>新增用户</el-button><el-button type="success" @click="handleExport"><el-icon><Download /></el-icon>导出CSV</el-button><el-upload:show-file-list="false":before-upload="handleImport"accept=".csv"style="display: inline-block; margin-left: 10px"><el-button type="warning"><el-icon><Upload /></el-icon>导入CSV</el-button></el-upload></el-col></el-row></el-card><!-- 数据表格 --><el-card class="table-card"><el-table:data="tableData"v-loading="loading"stripestyle="width: 100%"><el-table-column prop="id" label="ID" width="80" /><el-table-column prop="username" label="用户名" /><el-table-column prop="email" label="邮箱" /><el-table-column prop="phone" label="手机号" /><el-table-column prop="score" label="分数" width="80"><template #default="{ row }"><el-tag :type="getScoreType(row.score)">{{ row.score }}</el-tag></template></el-table-column><el-table-column prop="status" label="状态" width="100"><template #default="{ row }"><el-switchv-model="row.status":active-value="1":inactive-value="0"@change="handleStatusChange(row)"/></template></el-table-column><el-table-column prop="createTime" label="创建时间" width="180"><template #default="{ row }">{{ formatDate(row.createTime) }}</template></el-table-column><el-table-column label="操作" width="200" fixed="right"><template #default="{ row }"><el-button type="primary" size="small" @click="showEditDialog(row)">编辑</el-button><el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button></template></el-table-column></el-table><!-- 分页 --><el-paginationv-model:current-page="pagination.pageNum"v-model:page-size="pagination.pageSize":page-sizes="[10, 20, 50, 100]":total="pagination.total"layout="total, sizes, prev, pager, next, jumper"@size-change="handleSizeChange"@current-change="handleCurrentChange"style="margin-top: 20px"/></el-card><!-- 新增/编辑对话框 --><el-dialogv-model="dialogVisible":title="dialogTitle"width="500px"@close="resetForm"><el-formref="userFormRef":model="userForm":rules="rules"label-width="80px"><el-form-item label="用户名" prop="username"><el-inputv-model="userForm.username"placeholder="请输入用户名":disabled="isEdit"/></el-form-item><el-form-item label="密码" prop="password" v-if="!isEdit"><el-inputv-model="userForm.password"type="password"placeholder="请输入密码"show-password/></el-form-item><el-form-item label="邮箱" prop="email"><el-input v-model="userForm.email" placeholder="请输入邮箱" /></el-form-item><el-form-item label="手机号" prop="phone"><el-input v-model="userForm.phone" placeholder="请输入手机号" /></el-form-item><el-form-item label="分数" prop="score"><el-input-numberv-model="userForm.score":min="0":max="100"placeholder="请输入分数"/></el-form-item><el-form-item label="状态" prop="status"><el-radio-group v-model="userForm.status"><el-radio :value="1">启用</el-radio><el-radio :value="0">禁用</el-radio></el-radio-group></el-form-item></el-form><template #footer><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="submitForm">确定</el-button></template></el-dialog></div>
</template><script setup>
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {getUserList,createUser,updateUser,deleteUser,exportUsersCsv,importUsersCsv,
} from "@/api/user";// 数据定义
const loading = ref(false);
const tableData = ref([]);
const dialogVisible = ref(false);
const isEdit = ref(false);
const userFormRef = ref();// 搜索表单
const searchForm = reactive({username: "",
});// 分页
const pagination = reactive({pageNum: 1,pageSize: 10,total: 0,
});// 用户表单
const userForm = reactive({id: null,username: "",password: "",email: "",phone: "",score: 0,status: 1,
});// 表单验证规则
const rules = {username: [{ required: true, message: "请输入用户名", trigger: "blur" },{ min: 3, max: 20, message: "长度在 3 到 20 个字符", trigger: "blur" },],password: [{ required: true, message: "请输入密码", trigger: "blur" },{ min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" },],email: [{ required: true, message: "请输入邮箱", trigger: "blur" },{ type: "email", message: "请输入正确的邮箱地址", trigger: "blur" },],phone: [{pattern: /^1[3-9]\d{9}$/,message: "请输入正确的手机号",trigger: "blur",},],
};// 计算属性
const dialogTitle = ref("新增用户");// 方法
const getScoreType = (score) => {if (score >= 90) return "success";if (score >= 60) return "warning";return "danger";
};const formatDate = (dateStr) => {if (!dateStr) return "";const date = new Date(dateStr);return date.toLocaleString("zh-CN");
};// 获取用户列表
const getList = async () => {loading.value = true;try {const params = {pageNum: pagination.pageNum,pageSize: pagination.pageSize,};// 添加搜索参数if (searchForm.username) {params.username = searchForm.username;}const res = await getUserList(params);tableData.value = res.data.list;pagination.total = res.data.total;} catch (error) {ElMessage.error("获取用户列表失败");} finally {loading.value = false;}
};// 搜索
const handleSearch = () => {pagination.pageNum = 1;getList();
};// 重置搜索
const resetSearch = () => {searchForm.username = "";handleSearch();
};// 分页
const handleSizeChange = () => {pagination.pageNum = 1;getList();
};const handleCurrentChange = () => {getList();
};// 新增用户
const showAddDialog = () => {isEdit.value = false;dialogTitle.value = "新增用户";dialogVisible.value = true;
};// 编辑用户
const showEditDialog = (row) => {isEdit.value = true;dialogTitle.value = "编辑用户";Object.assign(userForm, row);dialogVisible.value = true;
};// 提交表单
const submitForm = async () => {const valid = await userFormRef.value.validate();if (!valid) return;try {if (isEdit.value) {await updateUser(userForm.id, userForm);ElMessage.success("更新成功");} else {await createUser(userForm);ElMessage.success("创建成功");}dialogVisible.value = false;getList();} catch (error) {ElMessage.error(error.message || "操作失败");}
};// 重置表单
const resetForm = () => {userFormRef.value?.resetFields();Object.assign(userForm, {id: null,username: "",password: "",email: "",phone: "",score: 0,status: 1,});
};// 状态切换
const handleStatusChange = async (row) => {try {await updateUser(row.id, { status: row.status });ElMessage.success("状态更新成功");} catch (error) {row.status = row.status === 1 ? 0 : 1;ElMessage.error("状态更新失败");}
};// 删除用户
const handleDelete = async (row) => {try {await ElMessageBox.confirm(`确定要删除用户 ${row.username} 吗?`,"删除确认",{confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",});await deleteUser(row.id);ElMessage.success("删除成功");getList();} catch (error) {if (error !== "cancel") {ElMessage.error("删除失败");}}
};// 导出CSV
const handleExport = async () => {try {const res = await exportUsersCsv();// 创建下载链接const url = window.URL.createObjectURL(new Blob([res], { type: "text/csv;charset=utf-8;" }));const link = document.createElement("a");link.href = url;link.download = `用户数据_${new Date().getTime()}.csv`;document.body.appendChild(link);link.click();// 清理setTimeout(() => {document.body.removeChild(link);window.URL.revokeObjectURL(url);}, 100);ElMessage.success("导出成功");} catch (error) {console.error("导出错误:", error);ElMessage.error("导出失败: " + (error.message || "未知错误"));}
};// 导入CSV
const handleImport = async (file) => {const formData = new FormData();formData.append("file", file);try {const res = await importUsersCsv(formData);const { successCount, errorCount, errors } = res.data;if (errorCount > 0) {ElMessage.warning(`导入完成:成功 ${successCount} 条,失败 ${errorCount} 条`);console.error("导入错误:", errors);} else {ElMessage.success(`导入成功:共 ${successCount} 条数据`);}getList();} catch (error) {ElMessage.error("导入失败");}return false; // 阻止默认上传行为
};// 生命周期
onMounted(() => {getList();
});
</script><style scoped lang="scss">
.user-management {.search-card {margin-bottom: 20px;}.table-card {.el-table {margin-bottom: 20px;}}
}
</style>