From d076b403c1c431d77385ddae8782ca3daf51a000 Mon Sep 17 00:00:00 2001 From: valarchie <343928303@qq.com> Date: Mon, 24 Jul 2023 19:35:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/user.ts | 122 ++++++++ src/layout/hooks/useNav.ts | 6 + src/views/system/user/form.vue | 209 +++++++++++++ src/views/system/user/hook.tsx | 389 +++++++++++++++++++++++++ src/views/system/user/passwordForm.vue | 48 +++ src/views/system/user/rule.ts | 37 +++ src/views/system/user/svg/expand.svg | 1 + src/views/system/user/svg/unexpand.svg | 1 + src/views/system/user/tree.vue | 212 ++++++++++++++ src/views/system/user/uploadForm.vue | 83 ++++++ 10 files changed, 1108 insertions(+) create mode 100644 src/api/system/user.ts create mode 100644 src/views/system/user/form.vue create mode 100644 src/views/system/user/hook.tsx create mode 100644 src/views/system/user/passwordForm.vue create mode 100644 src/views/system/user/rule.ts create mode 100644 src/views/system/user/svg/expand.svg create mode 100644 src/views/system/user/svg/unexpand.svg create mode 100644 src/views/system/user/tree.vue create mode 100644 src/views/system/user/uploadForm.vue diff --git a/src/api/system/user.ts b/src/api/system/user.ts new file mode 100644 index 0000000..27e2550 --- /dev/null +++ b/src/api/system/user.ts @@ -0,0 +1,122 @@ +import { http } from "@/utils/http"; + +export interface UserQuery extends BasePageQuery { + deptId?: number; + phoneNumber?: string; + status?: number; + userId?: number; + username?: string; +} + +/** + * UserDTO + */ +export interface UserDTO { + avatar?: string; + createTime?: Date; + creatorId?: number; + creatorName?: string; + deptId?: number; + deptName?: string; + email?: string; + loginDate?: Date; + loginIp?: string; + nickname?: string; + phoneNumber?: string; + postId?: number; + remark?: string; + roleId?: number; + roleName?: string; + sex?: number; + status?: number; + updaterId?: number; + updaterName?: string; + updateTime?: Date; + userId?: number; + username?: string; + userType?: number; +} + +/** + * AddUserCommand + */ +export interface UserRequest { + userId: number; + avatar?: string; + deptId?: number; + email?: string; + nickname?: string; + phoneNumber?: string; + password: string; + postId?: number; + remark?: string; + roleId?: number; + sex?: number; + status?: number; + username?: string; +} + +/** + * 修改密码 + */ +export interface PasswordRequest { + userId: number; + password: string; +} + +/** 获取用户列表 */ +export const getUserListApi = (params?: UserQuery) => { + return http.request>>("get", "/system/users", { + params + }); +}; + +/** 新增用户 */ +export const addUserApi = (data?: UserRequest) => { + return http.request>("post", "/system/users", { + data + }); +}; + +/** 编辑用户 */ +export const updateUserApi = (userId: number, data?: UserRequest) => { + return http.request>("put", `/system/users/${userId}`, { + data + }); +}; + +/** 更改用户密码 */ +export const updateUserPasswordApi = (data?: PasswordRequest) => { + return http.request>( + "put", + `/system/users/${data.userId}/password`, + { + data + } + ); +}; + +/** 删除用户 */ +export const deleteUserApi = (userId: number) => { + return http.request>("delete", `/system/users/${userId}`); +}; + +/** 修改用户状态 */ +export const updateUserStatusApi = (userId: number, status: number) => { + return http.request>>( + "put", + `/system/users/${userId}/status`, + { + data: { + status: status + } + } + ); +}; + +/** 批量导出用户 */ +export const exportUserExcelApi = (params: UserQuery, fileName: string) => { + return http.download("/system/users/excel", fileName, { + params + }); +}; diff --git a/src/layout/hooks/useNav.ts b/src/layout/hooks/useNav.ts index 52eca3b..d14e206 100644 --- a/src/layout/hooks/useNav.ts +++ b/src/layout/hooks/useNav.ts @@ -78,6 +78,11 @@ export function useNav() { router.push("/login"); } + /** 个人中心 */ + function userProfile() { + router.push("/system/user/profile"); + } + function backTopMenu() { router.push(getTopMenu()?.path); } @@ -121,6 +126,7 @@ export function useNav() { device, layout, logout, + userProfile, routers, $storage, backTopMenu, diff --git a/src/views/system/user/form.vue b/src/views/system/user/form.vue new file mode 100644 index 0000000..90fb2f0 --- /dev/null +++ b/src/views/system/user/form.vue @@ -0,0 +1,209 @@ + + + diff --git a/src/views/system/user/hook.tsx b/src/views/system/user/hook.tsx new file mode 100644 index 0000000..4006d78 --- /dev/null +++ b/src/views/system/user/hook.tsx @@ -0,0 +1,389 @@ +import dayjs from "dayjs"; +import { message } from "@/utils/message"; +import { + UserQuery, + getUserListApi, + addUserApi, + updateUserStatusApi, + updateUserApi, + exportUserExcelApi, + UserRequest, + deleteUserApi, + PasswordRequest, + updateUserPasswordApi +} from "@/api/system/user"; +import editForm from "./form.vue"; +import passwordForm from "./passwordForm.vue"; +import uploadForm from "./uploadForm.vue"; +import { ElMessageBox } from "element-plus"; +import { type PaginationProps } from "@pureadmin/table"; +import { reactive, ref, computed, onMounted, toRaw, h } from "vue"; +import { CommonUtils } from "@/utils/common"; +import { addDialog } from "@/components/ReDialog"; +import { handleTree, setDisabledForTreeOptions } from "@/utils/tree"; +import { getDeptListApi } from "@/api/system/dept"; +import { getPostListApi } from "@/api/system/post"; +import { getRoleListApi } from "@/api/system/role"; + +export function useHook() { + const searchFormParams = reactive({ + deptId: null, + phoneNumber: undefined, + status: undefined, + username: undefined, + timeRangeColumn: "createTime" + }); + + const formRef = ref(); + const timeRange = ref<[string, string]>(); + + const dataList = ref([]); + const pageLoading = ref(true); + const switchLoadMap = ref({}); + const pagination = reactive({ + total: 0, + pageSize: 10, + currentPage: 1, + background: true + }); + + const deptTreeList = ref([]); + const postOptions = ref([]); + const roleOptions = ref([]); + + const columns: TableColumnList = [ + { + label: "用户编号", + prop: "userId", + width: 90, + fixed: "left" + }, + { + label: "用户名", + prop: "username", + minWidth: 130 + }, + { + label: "昵称", + prop: "nickname", + minWidth: 130 + }, + { + label: "性别", + prop: "sex", + minWidth: 90, + cellRenderer: ({ row, props }) => ( + + {row.sex === 1 ? "男" : "女"} + + ) + }, + { + label: "部门ID", + prop: "deptId", + minWidth: 130, + hide: true + }, + { + label: "部门", + prop: "deptName", + minWidth: 130 + }, + { + label: "手机号码", + prop: "phoneNumber", + minWidth: 90 + }, + { + label: "角色ID", + prop: "roleId", + minWidth: 90, + hide: true + }, + { + label: "角色", + prop: "roleName", + minWidth: 90 + }, + { + label: "状态", + prop: "status", + minWidth: 90, + cellRenderer: scope => ( + onChange(scope as any)} + /> + ) + }, + { + label: "创建时间", + minWidth: 70, + prop: "createTime", + formatter: ({ createTime }) => + dayjs(createTime).format("YYYY-MM-DD HH:mm:ss") + }, + { + label: "操作", + fixed: "right", + width: 180, + slot: "operation" + } + ]; + const buttonClass = computed(() => { + return [ + "!h-[20px]", + "reset-margin", + "!text-gray-500", + "dark:!text-white", + "dark:hover:!text-primary" + ]; + }); + + function onChange({ row, index }) { + ElMessageBox.confirm( + `确认要${ + row.status === 0 ? "停用" : "启用" + }${ + row.username + }用户吗?`, + "系统提示", + { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning", + dangerouslyUseHTMLString: true, + draggable: true + } + ) + .then(async () => { + switchLoading(index, true); + await updateUserStatusApi(row.userId, row.status).finally(() => { + switchLoading(index, false); + }); + message("已成功修改用户状态", { + type: "success" + }); + }) + .catch(() => { + message("取消操作", { + type: "info" + }); + // 如果取消的话 恢复更改前的状态 + row.status === 0 ? (row.status = 1) : (row.status = 0); + }); + } + + function switchLoading(index: number, loading: boolean) { + switchLoadMap.value[index] = Object.assign({}, switchLoadMap.value[index], { + loading: loading + }); + } + + async function exportAllExcel() { + CommonUtils.fillPaginationParams(searchFormParams, pagination); + exportUserExcelApi(toRaw(searchFormParams), "用户列表.xls"); + } + + async function handleAdd(row, done) { + await addUserApi(row as UserRequest).then(() => { + message(`您新增了用户${row.username}的这条数据`, { + type: "success" + }); + // 关闭弹框 + done(); + // 刷新列表 + getList(); + }); + } + + async function handleUpdate(row, done) { + await updateUserApi(row.userId, row as UserRequest).then(() => { + message(`您修改了用户${row.username}的这条数据`, { + type: "success" + }); + // 关闭弹框 + done(); + // 刷新列表 + getList(); + }); + } + + async function handleDelete(row) { + await deleteUserApi(row.userId).then(() => { + message(`您删除了用户${row.username}的这条数据`, { type: "success" }); + // 刷新列表 + getList(); + }); + } + + async function handleResetPassword(row, request, done) { + await updateUserPasswordApi(request).then(() => { + message(`您修改了用户${row.username}的密码`, { type: "success" }); + // 刷新列表 + done(); + getList(); + }); + } + + async function onSearch() { + // 点击搜索的时候 需要重置分页 + pagination.currentPage = 1; + getList(); + } + + async function openDialog(title = "新增", row?: UserRequest) { + // TODO 如果是编辑的话 通过获取用户详情接口来获取数据 + addDialog({ + title: `${title}用户`, + props: { + formInline: { + userId: row?.userId ?? 0, + username: row?.username ?? "", + nickname: row?.nickname ?? "", + deptId: row?.deptId ?? undefined, + phoneNumber: row?.phoneNumber ?? "", + email: row?.email ?? "", + password: title == "新增" ? "" : undefined, + sex: row?.sex ?? undefined, + status: row?.status ?? undefined, + postId: row?.postId ?? undefined, + roleId: row?.roleId ?? undefined, + remark: row?.remark ?? "" + }, + deptOptions: deptTreeList, + postOptions: postOptions, + roleOptions: roleOptions + }, + + width: "40%", + draggable: true, + fullscreenIcon: true, + closeOnClickModal: false, + contentRenderer: () => h(editForm, { ref: formRef }), + beforeSure: (done, { options }) => { + const formRuleRef = formRef.value.getFormRuleRef(); + const curData = options.props.formInline as UserRequest; + + formRuleRef.validate(valid => { + if (valid) { + // 表单规则校验通过 + if (title === "新增") { + handleAdd(curData, done); + } else { + handleUpdate(curData, done); + } + } + }); + } + }); + } + + async function openResetPasswordDialog(row) { + const passwordFormRef = ref(); + addDialog({ + title: `重置密码`, + props: { + formInline: { + userId: row.userId ?? 0, + password: "" + } + }, + width: "30%", + closeOnClickModal: false, + contentRenderer: () => h(passwordForm, { ref: passwordFormRef }), + beforeSure: (done, { options }) => { + const formRef = passwordFormRef.value.getFormRuleRef(); + const curData = options.props.formInline as PasswordRequest; + + formRef.validate(valid => { + if (valid) { + handleResetPassword(row, curData, done); + } + }); + } + }); + } + + async function openUploadDialog() { + const uploadFormRef = ref(); + addDialog({ + title: `导入用户`, + props: {}, + width: "30%", + closeOnClickModal: false, + contentRenderer: () => h(uploadForm, { ref: uploadFormRef }), + beforeSure: done => { + console.log("上传文件"); + uploadFormRef.value.getFormRef().submit(); + done(); + getList(); + } + }); + } + + async function getList() { + CommonUtils.fillPaginationParams(searchFormParams, pagination); + CommonUtils.fillTimeRangeParams(searchFormParams, timeRange.value); + + pageLoading.value = true; + const { data } = await getUserListApi(toRaw(searchFormParams)).finally( + () => { + pageLoading.value = false; + } + ); + + dataList.value = data.rows; + pagination.total = data.total; + } + + const resetForm = formEl => { + if (!formEl) return; + formEl.resetFields(); + onSearch(); + }; + + onMounted(async () => { + onSearch(); + const deptResponse = getDeptListApi(); + deptTreeList.value = await setDisabledForTreeOptions( + handleTree(deptResponse.data), + "status" + ); + + const postResponse = await getPostListApi({}); + postOptions.value = postResponse.data.rows; + + const roleResponse = await getRoleListApi({}); + roleOptions.value = roleResponse.data.rows; + }); + + return { + searchFormParams, + pageLoading, + columns, + dataList, + pagination, + buttonClass, + onSearch, + openDialog, + exportAllExcel, + resetForm, + handleUpdate, + getList, + handleDelete, + openResetPasswordDialog, + openUploadDialog + }; +} diff --git a/src/views/system/user/passwordForm.vue b/src/views/system/user/passwordForm.vue new file mode 100644 index 0000000..72e1238 --- /dev/null +++ b/src/views/system/user/passwordForm.vue @@ -0,0 +1,48 @@ + + + diff --git a/src/views/system/user/rule.ts b/src/views/system/user/rule.ts new file mode 100644 index 0000000..b20bf67 --- /dev/null +++ b/src/views/system/user/rule.ts @@ -0,0 +1,37 @@ +import { reactive } from "vue"; +import type { FormRules } from "element-plus"; +import { isPhone, isEmail } from "@pureadmin/utils"; + +/** 自定义表单规则校验 */ +export const formRules = reactive({ + name: [{ required: true, message: "部门名称为必填项", trigger: "blur" }], + phone: [ + { + validator: (rule, value, callback) => { + if (value === "") { + callback(); + } else if (!isPhone(value)) { + callback(new Error("请输入正确的手机号码格式")); + } else { + callback(); + } + }, + trigger: "blur" + // trigger: "click" // 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可 + } + ], + email: [ + { + validator: (rule, value, callback) => { + if (value === "") { + callback(); + } else if (!isEmail(value)) { + callback(new Error("请输入正确的邮箱格式")); + } else { + callback(); + } + }, + trigger: "blur" + } + ] +}); diff --git a/src/views/system/user/svg/expand.svg b/src/views/system/user/svg/expand.svg new file mode 100644 index 0000000..dbbd4ed --- /dev/null +++ b/src/views/system/user/svg/expand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/views/system/user/svg/unexpand.svg b/src/views/system/user/svg/unexpand.svg new file mode 100644 index 0000000..58d4365 --- /dev/null +++ b/src/views/system/user/svg/unexpand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/views/system/user/tree.vue b/src/views/system/user/tree.vue new file mode 100644 index 0000000..bddd79f --- /dev/null +++ b/src/views/system/user/tree.vue @@ -0,0 +1,212 @@ + + + + + diff --git a/src/views/system/user/uploadForm.vue b/src/views/system/user/uploadForm.vue new file mode 100644 index 0000000..45a640c --- /dev/null +++ b/src/views/system/user/uploadForm.vue @@ -0,0 +1,83 @@ + + +