src/api/system/SysUserApi.ts
import { http } from "@/utils/http";
export type UserResult = {
success: boolean;
data: any;
};
export type UserPageResult = {
success: boolean;
data: {
rows: any[];
totalRows: number;
};
};
/** 获取用户分页列表 */
export const getUserPage = (params?: object) => {
return http.request<UserPageResult>("get", "/sys/user/page", { params });
};
/** 获取用户详情 */
export const getUserDetail = (data?: object) => {
return http.request<UserResult>("post", "/sys/user/detail", { data });
};
/** 新增用户 */
export const addUser = (data?: object) => {
return http.request<UserResult>("post", "/sys/user/add", { data });
};
/** 编辑用户 */
export const editUser = (data?: object) => {
return http.request<UserResult>("post", "/sys/user/edit", { data });
};
/** 删除用户 */
export const deleteUser = (data?: object) => {
return http.request<UserResult>("post", "/sys/user/delete", { data });
};src/views/system/user/index.vue
注意:前端 Vue 界面请根据数据库
sys_menu表中的uri路径创建,因为我们使用了前端模板的动态路由
<template>
<div class="main">
<el-card shadow="never">
<!-- 搜索区域 -->
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item label="搜索:">
<el-input
v-model="searchForm.searchText"
placeholder="请输入用户名或昵称"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleSearch">
搜索
</el-button>
<el-button :icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作按钮区域 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain :icon="Plus" @click="handleAdd">
新增
</el-button>
</el-col>
</el-row>
<!-- 数据表格 -->
<el-table
v-loading="loading"
:data="tableData"
border
stripe
style="width: 100%"
>
<el-table-column prop="userId" label="用户ID" width="100" />
<el-table-column prop="username" label="用户名" min-width="120" />
<el-table-column prop="nickName" label="昵称" min-width="120" />
<el-table-column prop="email" label="邮箱" min-width="150" />
<el-table-column prop="phone" label="手机号" min-width="120" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.statusFlag === '1' ? 'success' : 'danger'">
{{ row.statusFlag === "1" ? "启用" : "禁用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button
link
type="primary"
:icon="Edit"
@click="handleEdit(row)"
>
编辑
</el-button>
<el-button
link
type="danger"
:icon="Delete"
@click="handleDelete(row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
v-model:current-page="searchForm.pageNum"
v-model:page-size="searchForm.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
class="mt-4"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
<!-- 新增/编辑对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="600px"
@close="handleClose"
>
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="用户名" prop="username">
<el-input
v-model="formData.username"
placeholder="请输入用户名"
:disabled="isEdit"
/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="formData.password"
type="password"
:placeholder="isEdit ? '请输入密码(不填则不修改)' : '请输入密码'"
show-password
/>
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input v-model="formData.nickName" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="状态" prop="statusFlag">
<el-radio-group v-model="formData.statusFlag">
<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="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Delete, Edit, Search, Refresh } from "@element-plus/icons-vue";
import {
getUserPage,
addUser,
editUser,
deleteUser,
getUserDetail
} from "@/api/system/SysUserApi";
// 定义组件选项
defineOptions({
// 设置组件名称
name: "UserManage"
});
/** 加载状态 */
const loading = ref(false);
/** 表格数据 */
const tableData = ref([]);
/** 总记录数 */
const total = ref(0);
/** 对话框显示状态 */
const dialogVisible = ref(false);
/** 对话框标题 */
const dialogTitle = ref("");
/** 表单引用 */
const formRef = ref();
/** 搜索表单数据 */
const searchForm = reactive({
searchText: "",
pageNum: 1,
pageSize: 10
});
/** 编辑/新增表单数据 */
const formData = reactive({
userId: undefined,
username: "",
password: "",
nickName: "",
email: "",
phone: "",
statusFlag: "1"
});
/** 表单验证规则 */
const rules = reactive({
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
nickName: [{ required: true, message: "请输入昵称", trigger: "blur" }]
});
/** 是否为编辑模式 */
const isEdit = ref(false);
/**
* 加载用户列表数据
*/
const loadData = async () => {
loading.value = true;
try {
const res = await getUserPage(searchForm);
if (res.success) {
tableData.value = res.data.rows || [];
total.value = res.data.totalRows || 0;
}
} catch (error) {
console.error("加载数据失败", error);
ElMessage.error("加载数据失败");
} finally {
loading.value = false;
}
};
/**
* 搜索用户
*/
const handleSearch = () => {
searchForm.pageNum = 1;
loadData();
};
/**
* 重置搜索条件
*/
const handleReset = () => {
searchForm.searchText = "";
searchForm.pageNum = 1;
loadData();
};
/**
* 打开新增用户对话框
*/
const handleAdd = () => {
isEdit.value = false;
dialogTitle.value = "新增用户";
resetForm();
updateRules();
dialogVisible.value = true;
};
/**
* 打开编辑用户对话框
* @param row 当前行数据
*/
const handleEdit = async (row: any) => {
isEdit.value = true;
dialogTitle.value = "编辑用户";
try {
const res = await getUserDetail({ userId: row.userId });
if (res.success) {
Object.assign(formData, res.data);
// 编辑时不显示密码,保持为空
formData.password = "";
updateRules();
dialogVisible.value = true;
}
} catch (error) {
console.error("获取用户详情失败", error);
ElMessage.error("获取用户详情失败");
}
};
/**
* 删除用户
* @param row 当前行数据
*/
const handleDelete = (row: any) => {
ElMessageBox.confirm("确定要删除该用户吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(async () => {
try {
const res = await deleteUser({ userId: row.userId });
if (res.success) {
ElMessage.success("删除成功");
loadData();
}
} catch (error) {
console.error("删除失败", error);
ElMessage.error("删除失败");
}
})
.catch(() => {
// 用户取消删除
});
};
/**
* 提交表单(新增或编辑)
*/
const handleSubmit = async () => {
if (!formRef.value) return;
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
try {
// 创建提交数据的副本
const submitData = { ...formData };
// 编辑模式下,如果密码为空则不传递该字段
if (isEdit.value && !submitData.password) {
delete submitData.password;
}
// 转换状态标识为字符类型,匹配后端 Character 类型
if (submitData.statusFlag) {
submitData.statusFlag = String(submitData.statusFlag).charAt(0);
}
// 根据模式调用对应的 API
const api = isEdit.value ? editUser : addUser;
const res = await api(submitData);
if (res.success) {
ElMessage.success(isEdit.value ? "修改成功" : "新增成功");
dialogVisible.value = false;
loadData();
}
} catch (error) {
console.error(isEdit.value ? "修改失败" : "新增失败", error);
ElMessage.error(isEdit.value ? "修改失败" : "新增失败");
}
}
});
};
/**
* 关闭对话框并重置表单
*/
const handleClose = () => {
dialogVisible.value = false;
resetForm();
};
/**
* 重置表单数据
*/
const resetForm = () => {
Object.assign(formData, {
userId: undefined,
username: "",
password: "",
nickName: "",
email: "",
phone: "",
statusFlag: "1"
});
formRef.value?.clearValidate();
};
/**
* 根据模式更新表单验证规则
* 新增模式:密码必填
* 编辑模式:密码非必填
*/
const updateRules = () => {
if (isEdit.value) {
// 编辑模式:密码可选
rules.password = [];
} else {
// 新增模式:密码必填
rules.password = [
{ required: true, message: "请输入密码", trigger: "blur" }
];
}
};
/**
* 分页:每页条数改变
* @param val 新的每页条数
*/
const handleSizeChange = (val: number) => {
searchForm.pageSize = val;
loadData();
};
/**
* 分页:当前页改变
* @param val 新的页码
*/
const handleCurrentChange = (val: number) => {
searchForm.pageNum = val;
loadData();
};
// 组件挂载时加载数据
onMounted(() => {
loadData();
});
</script>
<style scoped lang="scss">
.main {
padding: 20px;
}
.search-form {
margin-bottom: 20px;
}
.mb8 {
margin-bottom: 8px;
}
.mt-4 {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
</style>管理界面示例图

新增 / 编辑示例图


由于 JavaScript 的 Number 类型无法表示大整数,在实体类中添加 Jackson 注解,将 Long 类型序列化为字符串
修改 com.lusifer.crmeb.entity.SysUser 在数据类型为 Long 的字段上追加 Jackson 注解
public class SysUser extends BaseBusinessEntity {
/** 用户ID */
@TableId(value = "user_id", type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long userId;
/** 所属部门 */
@JsonSerialize(using = ToStringSerializer.class)
private Long deptId;
}上述操作完成后,在用户管理界面测试 CRUD 功能