feat: 给用户管理、部门管理、权限管理、系统配置、国际化配置、角色管理、登录日志、操作日志、缓存列表、缓存监控和性能监控添加按钮级权限管理;调整加载个人信息逻辑
This commit is contained in:
		
							parent
							
								
									475a8e4058
								
							
						
					
					
						commit
						c0c127d090
					
				| @ -22,6 +22,8 @@ buttons:OpenText: Open | ||||
| buttons:CloseText: Close | ||||
| buttons:Search: Search | ||||
| buttons:Reset: Reset | ||||
| buttons:Details: Details | ||||
| buttons:DataList: DataList | ||||
| buttons:Add: Add | ||||
| buttons:Update: Update | ||||
| buttons:Delete: Delete | ||||
| @ -40,13 +42,13 @@ buttons:More: More | ||||
| buttons:Deselect: Deselect | ||||
| buttons:DeleteInBatches: Delete In Batches | ||||
| buttons:ExportInBatches: Export In Batches | ||||
| buttons:ExitInBatches: Exit In Batches | ||||
| buttons:ExportAll: Export All | ||||
| buttons:UploadAvatar: Upload Avatar | ||||
| buttons:ResetPassword: Reset Password | ||||
| buttons:RoleAllocation: Role Allocation | ||||
| buttons:PermissionDetails: Permission Details | ||||
| buttons:ForceToExit: Force Exit | ||||
| buttons:Details: Details | ||||
| search:Total: Total | ||||
| search:History: History | ||||
| search:Collect: Collect | ||||
| @ -174,3 +176,16 @@ logout:message: Whether to exit the system? | ||||
| logout:success: Logout Success | ||||
| logout:fail: Logout Fail | ||||
| logout:cancel: Logout Cancel | ||||
| user:buttons:addRole: Add User Role | ||||
| user:buttons:deleteRole: Delete User Role | ||||
| user:buttons:updateRole: Update User Role | ||||
| user:buttons:roleInfo: User Role Info | ||||
| user:buttons:roleDataList: User Role Data List | ||||
| user:buttons:permisssionList: User Permisssion List | ||||
| user:buttons:uploadAvatar: Upload Avatar | ||||
| user:buttons:resetPassword: Reset Password | ||||
| role:buttons:addPermission:  Add Role Permission | ||||
| role:buttons:deletePermission: Delete Role Permission | ||||
| role:buttons:updatePermission: Update Role Permission | ||||
| role:buttons:permissiomInfo: Role Permissiom Info | ||||
| role:buttons:permissionDataList: Rolr Permission Data List | ||||
|  | ||||
| @ -22,6 +22,8 @@ buttons:OpenText: 开 | ||||
| buttons:CloseText: 关 | ||||
| buttons:Search: 搜索 | ||||
| buttons:Reset: 重置 | ||||
| buttons:Details: 详情 | ||||
| buttons:DataList: 数据列表 | ||||
| buttons:Add: 添加 | ||||
| buttons:Update: 修改 | ||||
| buttons:Delete: 删除 | ||||
| @ -46,7 +48,7 @@ buttons:ResetPassword: 重置密码 | ||||
| buttons:RoleAllocation: 角色分配 | ||||
| buttons:PermissionDetails: 权限详情 | ||||
| buttons:ForceToExit: 强制退出 | ||||
| buttons:Details: 详情 | ||||
| buttons:ExitInBatches: 批量强退 | ||||
| search:Total: 共 | ||||
| search:History: 搜索历史 | ||||
| search:Collect: 收藏 | ||||
| @ -174,3 +176,16 @@ logout:message: 是否退出当前系统? | ||||
| logout:success: 退出成功 | ||||
| logout:fail: 退出失败 | ||||
| logout:cancel: 退出取消 | ||||
| user:buttons:addRole: 添加用户角色 | ||||
| user:buttons:deleteRole: 删除用户角色 | ||||
| user:buttons:updateRole: 更新用户角色 | ||||
| user:buttons:roleInfo: 用户角色详情 | ||||
| user:buttons:roleDataList: 用户角色列表 | ||||
| user:buttons:permisssionList: 用户权限列表 | ||||
| user:buttons:uploadAvatar: 上传头像 | ||||
| user:buttons:resetPassword: 重置密码 | ||||
| role:buttons:addPermission: 添加角色权限 | ||||
| role:buttons:deletePermission: 删除角色权限 | ||||
| role:buttons:updatePermission: 更新角色权限 | ||||
| role:buttons:permissiomInfo: 角色权限详情 | ||||
| role:buttons:permissionDataList: 角色权限列表 | ||||
|  | ||||
| @ -31,6 +31,18 @@ export const deleteLocaleAPI = (id: string) => { | ||||
|   return http.request<null>("post", `/api/i18n/deleteLocale/${id}`); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  * 批量删除语言类型 | ||||
|  */ | ||||
| export const deleteLocaleListAPI = (ids: string[]) => { | ||||
|   return http.request<null>("post", `/api/i18n/deleteLocaleList`, { | ||||
|     data: { | ||||
|       ids | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 修改语言类型 | ||||
|  */ | ||||
| @ -138,6 +150,18 @@ export const getI18nInfoAPI = (id: string) => { | ||||
| export const deleteI18nAPI = (id: string) => { | ||||
|   return http.request<null>("post", `/api/i18n/deleteI18n/${id}`); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  * 批量删除翻译 | ||||
|  */ | ||||
| export const deleteI18nListAPI = (ids: string[]) => { | ||||
|   return http.request<null>("post", `/api/i18n/deleteI18nList`, { | ||||
|     data: { | ||||
|       ids | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
| /** | ||||
|  * 修改翻译 | ||||
|  * @param data | ||||
|  | ||||
| @ -14,10 +14,27 @@ import { filterEmptyObject } from "./utils"; | ||||
|  * 用户获取登录日志 | ||||
|  */ | ||||
| 
 | ||||
| export const getUserLoginLogAPI = (params: { | ||||
| /**获取用户登录日志参数 */ | ||||
| type GetUserLoginLogParams = { | ||||
|   /**页码 */ | ||||
|   page: number; | ||||
|   /**每页数量 */ | ||||
|   pageSize: number; | ||||
| }) => { | ||||
|   /**用户账号 */ | ||||
|   username?: string; | ||||
|   /**用户昵称 */ | ||||
|   nickname?: string; | ||||
|   /**部门ID */ | ||||
|   department_id?: string; | ||||
|   /**登录状态 */ | ||||
|   status?: number | string; | ||||
|   /**开始时间 */ | ||||
|   startTime?: string | number; | ||||
|   /**结束时间 */ | ||||
|   endTime?: string | number; | ||||
| }; | ||||
| 
 | ||||
| export const getUserLoginLogAPI = (params: GetUserLoginLogParams) => { | ||||
|   return http.request<QueryListResult<UserLoginLogInfo>>( | ||||
|     "get", | ||||
|     "/api/log/login", | ||||
| @ -36,24 +53,73 @@ export const deleteUserOnlineAPI = (id: string) => { | ||||
|   return http.request<null>("delete", `/api/log/logout/${id}`); | ||||
| }; | ||||
| 
 | ||||
| /**用户批量强退*/ | ||||
| export const deleteUserOnlineListAPI = (data: { ids: string[] }) => { | ||||
|   return http.request<null>("delete", `/api/log/logoutList`, { data }); | ||||
| }; | ||||
| 
 | ||||
| /**删除登录日志 */ | ||||
| export const deleteUserLoginLogAPI = (id: string) => { | ||||
|   return http.request<null>("delete", `/api/log/delete/login/${id}`); | ||||
| }; | ||||
| /**批量删除登录日志 */ | ||||
| export const deleteUserLoginLogListAPI = (data: { ids: string[] }) => { | ||||
|   return http.request<null>("delete", `/api/log/deleteList/login`, { data }); | ||||
| }; | ||||
| 
 | ||||
| // ------------------------操作日志相关----------------------------------------
 | ||||
| 
 | ||||
| /**获取操作日志参数 */ | ||||
| type GetUserOperationParams = { | ||||
|   /**页码 */ | ||||
|   page: number; | ||||
|   /**每页数量 */ | ||||
|   pageSize: number; | ||||
|   /**用户账号 */ | ||||
|   username?: string; | ||||
|   /**用户昵称 */ | ||||
|   nickname?: string; | ||||
|   /**部门ID */ | ||||
|   department_id?: string; | ||||
|   /**操作名称 */ | ||||
|   name?: string; | ||||
|   /**操作状态 */ | ||||
|   status?: number | string; | ||||
|   /**操作类型 */ | ||||
|   type?: number | string; | ||||
|   /**开始时间 */ | ||||
|   startTime?: string | number; | ||||
|   /**结束时间 */ | ||||
|   endTime?: string | number; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 获取用户操作日志 | ||||
|  */ | ||||
| export const getUserOperationsAPI = (params: { | ||||
|   page: number; | ||||
|   pageSize: number; | ||||
| }) => { | ||||
| export const getUserOperationsAPI = (params: GetUserOperationParams) => { | ||||
|   return http.request<QueryListResult<OperationLogInfo>>( | ||||
|     "GET", | ||||
|     "/api/log/operation", | ||||
|     { | ||||
|       params | ||||
|       params: filterEmptyObject(params) | ||||
|     } | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 删除操作日志 | ||||
|  * @returns | ||||
|  */ | ||||
| export const deleteUserOperationsAPI = (id: string) => { | ||||
|   return http.request<null>("delete", `/api/log/delete/operation/${id}`); | ||||
| }; | ||||
| /**批量删除操作日志 */ | ||||
| export const deleteUserOperationsListAPI = (data: { ids: string[] }) => { | ||||
|   return http.request<null>("delete", `/api/log/deleteList/operation`, { | ||||
|     data | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // ------------------------服务相关----------------------------------------
 | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -318,6 +318,15 @@ export const deleteRoleAPI = (id: string) => { | ||||
|   return http.request<null>("post", `/api/role/delete/${id}`); | ||||
| }; | ||||
| 
 | ||||
| /**批量删除角色 */ | ||||
| export const deleteRoleListAPI = (ids: string[]) => { | ||||
|   return http.request<null>("post", `/api/role/deleteList`, { | ||||
|     data: { | ||||
|       ids | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // --------------------------用户相关--------------------------------------
 | ||||
| 
 | ||||
| /**添加用户参数 */ | ||||
| @ -429,7 +438,7 @@ export const deleteUserAPI = (id: string) => { | ||||
|  * @param  data 用户ID列表 | ||||
|  * @returns | ||||
|  */ | ||||
| export const deleteUserListAPI = (data: { userIds: string[] }) => { | ||||
| export const deleteUserListAPI = (data: { ids: string[] }) => { | ||||
|   return http.request<null>("post", `/api/user/deleteUserList`, { data }); | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -27,7 +27,8 @@ import { | ||||
|   type UserInfo, | ||||
|   userInfoKey, | ||||
|   removeToken, | ||||
|   getTokenInfo | ||||
|   getTokenInfo, | ||||
|   getUserInfo | ||||
| } from "@/utils/auth"; | ||||
| 
 | ||||
| /** 自动导入全部静态路由,无需再手动引入!匹配 src/router/modules 目录(任何嵌套级别)中具有 .ts 扩展名的所有文件,除了 remaining.ts 文件 | ||||
| @ -129,6 +130,8 @@ router.beforeEach((to: ToRouteType, _from, next) => { | ||||
|     whiteList.includes(to.fullPath) ? next(_from.fullPath) : next(); | ||||
|   } | ||||
|   const data = getTokenInfo(); | ||||
|   /**获取个人信息 */ | ||||
|   getUserInfo(); | ||||
|   if (!data.isExpire) { | ||||
|     // 无权限跳转403页面
 | ||||
|     if ( | ||||
|  | ||||
| @ -150,7 +150,7 @@ export const useUserStore = defineStore({ | ||||
|       return new Promise<ResponseResult<LoginResult>>((resolve, reject) => { | ||||
|         getLogin(data) | ||||
|           .then(data => { | ||||
|             if (data.code === 200) { | ||||
|             if (data.success) { | ||||
|               setToken(data.data); | ||||
|             } | ||||
|             resolve(data); | ||||
| @ -189,7 +189,7 @@ export const useUserStore = defineStore({ | ||||
|       return new Promise<ResponseResult<LoginResult>>((resolve, reject) => { | ||||
|         refreshTokenApi(data) | ||||
|           .then(data => { | ||||
|             if (data.code === 200) { | ||||
|             if (data.success) { | ||||
|               setToken(data.data); | ||||
|             } | ||||
|             resolve(data); | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import { storageLocal } from "@pureadmin/utils"; | ||||
| import { useUserStoreHook } from "@/store/modules/user"; | ||||
| import { getUserInfoAPI } from "@/api/login"; | ||||
| 
 | ||||
| export interface UserInfo { | ||||
|   /** 用户名 */ | ||||
| @ -109,3 +110,21 @@ export function getTokenInfo(): { | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /**判断是否有权限 */ | ||||
| export const hasAuth = (auth: string) => { | ||||
|   return useUserStoreHook().permissions.includes(auth); | ||||
| }; | ||||
| 
 | ||||
| /**获取用户信息 */ | ||||
| export const getUserInfo = async () => { | ||||
|   const res = await getUserInfoAPI(); | ||||
|   if (res.success) { | ||||
|     const user = storageLocal().getItem<object>(userInfoKey); | ||||
|     storageLocal().setItem(userInfoKey, { | ||||
|       ...user, | ||||
|       ...res.data | ||||
|     }); | ||||
|     setUserInfo(res.data); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| <script setup lang="ts"> | ||||
| import { ref } from "vue"; | ||||
| import { message } from "@/utils/message"; | ||||
| import Avatar from "@/assets/user.png"; | ||||
| import type { FormInstance } from "element-plus"; | ||||
| import ReCropperPreview from "@/components/ReCropperPreview"; | ||||
| import { deviceDetection } from "@pureadmin/utils"; | ||||
| @ -47,7 +48,7 @@ const handleSubmitImage = async () => { | ||||
|   const res = await postUploadAvatarAPI(userInfo.id, { | ||||
|     file: cropperBlob.value | ||||
|   }); | ||||
|   if (res.code === 200) { | ||||
|   if (res.success) { | ||||
|     message("更新头像成功", { type: "success" }); | ||||
|     userInfo.avatar = `/file/${res.data.id}`; | ||||
|     const user = storageLocal().getItem<object>(userInfoKey); | ||||
| @ -71,7 +72,7 @@ const onSubmit = async (formEl: FormInstance) => { | ||||
|         gender: userInfo.gender | ||||
|       }; | ||||
|       const res = await putUpdateBaseUserInfoAPI(updateForm); | ||||
|       if (res.code === 200) { | ||||
|       if (res.success) { | ||||
|         message(res.msg, { type: "success" }); | ||||
|         await getUserInfo(); | ||||
|       } else { | ||||
| @ -94,7 +95,10 @@ const onSubmit = async (formEl: FormInstance) => { | ||||
|     <h3 class="my-8">个人信息</h3> | ||||
|     <el-form ref="userInfoFormRef" label-position="top" :model="userInfo"> | ||||
|       <el-form-item label="头像"> | ||||
|         <el-avatar :size="80" :src="`/api/${userInfo.avatar}`" /> | ||||
|         <el-avatar | ||||
|           :size="80" | ||||
|           :src="userInfo.avatar ? `/api/${userInfo.avatar}` : Avatar" | ||||
|         /> | ||||
|         <el-upload | ||||
|           ref="uploadRef" | ||||
|           accept="image/*" | ||||
|  | ||||
| @ -2,7 +2,6 @@ import { message } from "@/utils/message"; | ||||
| import { addDialog } from "@/components/ReDialog"; | ||||
| import { reactive, ref, onMounted, watch } from "vue"; | ||||
| import { ElForm, ElFormItem, ElInput, ElProgress } from "element-plus"; | ||||
| import type { UserInfo } from "types/user"; | ||||
| import { getUserInfoAPI } from "@/api/login"; | ||||
| import { | ||||
|   putUpdateEmailAPI, | ||||
| @ -12,6 +11,7 @@ import { | ||||
| import { isAllEmpty, isEmail, isPhone, storageLocal } from "@pureadmin/utils"; | ||||
| import { zxcvbn } from "@zxcvbn-ts/core"; | ||||
| import { setUserInfo, userInfoKey } from "@/utils/auth"; | ||||
| import type { UserInfo } from "types/system"; | ||||
| 
 | ||||
| export const useUserInfo = () => { | ||||
|   /** 密码正则(密码格式应为8-18位数字、字母、符号的任意两种组合) */ | ||||
| @ -52,10 +52,9 @@ export const useUserInfo = () => { | ||||
|     nickname: "", | ||||
|     phone: "", | ||||
|     status: 0, | ||||
|     department_id: "", | ||||
|     create_time: "", | ||||
|     update_time: "", | ||||
|     roles: [], | ||||
|     permissions: [] | ||||
|     update_time: "" | ||||
|   }); | ||||
|   /**获取个人信息 */ | ||||
|   const getUserInfo = async () => { | ||||
| @ -200,7 +199,7 @@ export const useUserInfo = () => { | ||||
|           if (valid) { | ||||
|             // 表单规则校验通过
 | ||||
|             const res = await putUpdatePasswordAPI(passwordForm); | ||||
|             if (res.code === 200) { | ||||
|             if (res.success) { | ||||
|               done(); | ||||
|               message(res.msg, { | ||||
|                 type: "success" | ||||
| @ -262,7 +261,7 @@ export const useUserInfo = () => { | ||||
|           if (valid) { | ||||
|             // 表单规则校验通过
 | ||||
|             const res = await putUpdatePhoneAPI(phoneForm); | ||||
|             if (res.code === 200) { | ||||
|             if (res.success) { | ||||
|               done(); | ||||
|               message(res.msg, { | ||||
|                 type: "success" | ||||
| @ -323,7 +322,7 @@ export const useUserInfo = () => { | ||||
|           if (valid) { | ||||
|             // 表单规则校验通过
 | ||||
|             const res = await putUpdateEmailAPI(emailForm); | ||||
|             if (res.code === 200) { | ||||
|             if (res.success) { | ||||
|               done(); | ||||
|               message(res.msg, { | ||||
|                 type: "success" | ||||
|  | ||||
| @ -91,7 +91,7 @@ const onUpdate = async (formEl: FormInstance | undefined) => { | ||||
|           code: ruleForm.code, | ||||
|           department_id: ruleForm.department_id | ||||
|         }); | ||||
|         if (res.code === 200) { | ||||
|         if (res.success) { | ||||
|           message(transformI18n($t("login:RegisterSuccess")), { | ||||
|             type: "success" | ||||
|           }); | ||||
| @ -124,10 +124,10 @@ const start = async ( | ||||
|     if (isValid) { | ||||
|       const res = await postGetCodeAPI({ | ||||
|         username: ruleForm.username, | ||||
|         title: "注册", | ||||
|         title: "Register", | ||||
|         mail: ruleForm.email | ||||
|       }); | ||||
|       if (res.code === 200) { | ||||
|       if (res.success) { | ||||
|         clearInterval(timer.value); | ||||
|         isDisabled.value = true; | ||||
|         text.value = `${time}`; | ||||
|  | ||||
| @ -81,10 +81,10 @@ const start = async ( | ||||
|     if (isValid) { | ||||
|       const res = await postGetCodeAPI({ | ||||
|         username: ruleForm.username, | ||||
|         title: "重置", | ||||
|         title: "Reset", | ||||
|         mail: ruleForm.email | ||||
|       }); | ||||
|       if (res.code === 200) { | ||||
|       if (res.success) { | ||||
|         clearInterval(timer.value); | ||||
|         isDisabled.value = true; | ||||
|         text.value = `${time}`; | ||||
|  | ||||
| @ -34,8 +34,6 @@ import Check from "@iconify-icons/ep/check"; | ||||
| import User from "@iconify-icons/ri/user-3-fill"; | ||||
| import Info from "@iconify-icons/ri/information-line"; | ||||
| import { GetCaptchaAPI } from "@/api/login"; | ||||
| import { getUserInfoAPI } from "@/api/login"; | ||||
| import { setUserInfo } from "@/utils/auth"; | ||||
| const Period = getConfig("Period"); | ||||
| 
 | ||||
| defineOptions({ | ||||
| @ -88,10 +86,6 @@ const onLogin = async (formEl: FormInstance | undefined) => { | ||||
|             // 获取后端路由 | ||||
|             return initRouter().then(async () => { | ||||
|               disabled.value = true; | ||||
|               const res = await getUserInfoAPI(); | ||||
|               if (res.success) { | ||||
|                 setUserInfo(res.data); | ||||
|               } | ||||
|               router | ||||
|                 .push(getTopMenu(true).path) | ||||
|                 .then(() => { | ||||
|  | ||||
							
								
								
									
										10
									
								
								src/views/monitor/cache/index.vue
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								src/views/monitor/cache/index.vue
									
									
									
									
										vendored
									
									
								
							| @ -121,6 +121,7 @@ import * as echarts from "echarts"; | ||||
| import Monitor from "@iconify-icons/ep/monitor"; | ||||
| import PieChart from "@iconify-icons/ep/pie-chart"; | ||||
| import Odometer from "@iconify-icons/ep/odometer"; | ||||
| import { onBeforeRouteLeave } from "vue-router"; | ||||
| 
 | ||||
| const cache = reactive({ | ||||
|   commandStats: [] as { name: string; value: number }[], | ||||
| @ -240,13 +241,18 @@ const initUsedMemoryChart = () => { | ||||
| 
 | ||||
|   chart.setOption(option); | ||||
| }; | ||||
| 
 | ||||
| /**定时ID */ | ||||
| const timer = ref(); | ||||
| onMounted(async () => { | ||||
|   await getCacheInfo(); | ||||
|   setInterval(() => { | ||||
|   timer.value = setInterval(() => { | ||||
|     getCacheInfo(); | ||||
|   }, 60000); | ||||
| }); | ||||
| /**页面离开时清除定时器 */ | ||||
| onBeforeRouteLeave(() => { | ||||
|   clearInterval(timer.value); | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"> | ||||
|  | ||||
							
								
								
									
										6
									
								
								src/views/monitor/cache/list.vue
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								src/views/monitor/cache/list.vue
									
									
									
									
										vendored
									
									
								
							| @ -13,6 +13,7 @@ | ||||
|                 link | ||||
|                 type="primary" | ||||
|                 size="large" | ||||
|                 :disabled="!hasAuth('cache:btn:list')" | ||||
|                 :icon="useRenderIcon(Refresh)" | ||||
|                 @click="refreshCacheNames()" | ||||
|               /> | ||||
| @ -61,6 +62,7 @@ | ||||
|                     <el-button | ||||
|                       link | ||||
|                       type="primary" | ||||
|                       :disabled="!hasAuth('cache:btn:delete')" | ||||
|                       :icon="useRenderIcon(Delete)" | ||||
|                     /> | ||||
|                   </template> | ||||
| @ -83,6 +85,7 @@ | ||||
|                 type="primary" | ||||
|                 link | ||||
|                 size="large" | ||||
|                 :disabled="!hasAuth('cache:btn:list')" | ||||
|                 :icon="useRenderIcon(Refresh)" | ||||
|                 @click="refreshCacheKeys()" | ||||
|               /> | ||||
| @ -122,6 +125,7 @@ | ||||
|                     <el-button | ||||
|                       link | ||||
|                       type="primary" | ||||
|                       :disabled="!hasAuth('cache:btn:delete')" | ||||
|                       :icon="useRenderIcon(Delete)" | ||||
|                     /> | ||||
|                   </template> | ||||
| @ -153,6 +157,7 @@ | ||||
|                     style="float: right; padding: 3px 0" | ||||
|                     link | ||||
|                     type="primary" | ||||
|                     :disabled="!hasAuth('cache:btn:delete')" | ||||
|                     :icon="useRenderIcon(Delete)" | ||||
|                     >清理全部</el-button | ||||
|                   > | ||||
| @ -207,6 +212,7 @@ import Document from "@iconify-icons/ep/document"; | ||||
| import Delete from "@iconify-icons/ep/delete"; | ||||
| import Refresh from "@iconify-icons/ep/refresh"; | ||||
| import { message } from "@/utils/message"; | ||||
| import { hasAuth } from "@/utils/auth"; | ||||
| defineOptions({ | ||||
|   name: "CacheList" | ||||
| }); | ||||
|  | ||||
| @ -5,9 +5,10 @@ import { getPickerShortcuts } from "../utils"; | ||||
| import { PureTableBar } from "@/components/RePureTableBar"; | ||||
| import { useRenderIcon } from "@/components/ReIcon/src/hooks"; | ||||
| import { useI18n } from "vue-i18n"; | ||||
| import Delete from "@iconify-icons/ep/delete"; | ||||
| import Plane from "@iconify-icons/ri/plane-line"; | ||||
| import Refresh from "@iconify-icons/ep/refresh"; | ||||
| 
 | ||||
| import { hasAuth } from "@/utils/auth"; | ||||
| defineOptions({ | ||||
|   name: "MonitorLogin" | ||||
| }); | ||||
| @ -23,13 +24,16 @@ const { | ||||
|   dataList, | ||||
|   pagination, | ||||
|   selectedNum, | ||||
|   departments, | ||||
|   onSearch, | ||||
|   deleteUserHandle, | ||||
|   resetForm, | ||||
|   onbatchDel, | ||||
|   onbatchForce, | ||||
|   resetForm, | ||||
|   deleteUserHandle, | ||||
|   onSelectionCancel, | ||||
|   handleCurrentChange, | ||||
|   handleSizeChange, | ||||
|   handleDelete, | ||||
|   handleSelectionChange | ||||
| } = useLogin(tableRef); | ||||
| </script> | ||||
| @ -42,12 +46,20 @@ const { | ||||
|       :model="form" | ||||
|       class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto" | ||||
|     > | ||||
|       <el-form-item label="用户名" prop="username"> | ||||
|       <el-form-item label="用户账号" prop="username"> | ||||
|         <el-input | ||||
|           v-model="form.username" | ||||
|           placeholder="请输入用户名" | ||||
|           placeholder="请输入用户账号~" | ||||
|           clearable | ||||
|           class="!w-[150px]" | ||||
|           class="!w-[200px]" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="用户名称" prop="nickname"> | ||||
|         <el-input | ||||
|           v-model="form.nickname" | ||||
|           placeholder="请输入用户名称~" | ||||
|           clearable | ||||
|           class="!w-[200px]" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="登录状态" prop="status"> | ||||
| @ -55,12 +67,33 @@ const { | ||||
|           v-model="form.status" | ||||
|           placeholder="请选择" | ||||
|           clearable | ||||
|           class="!w-[150px]" | ||||
|           class="!w-[200px]" | ||||
|         > | ||||
|           <el-option label="成功" value="1" /> | ||||
|           <el-option label="失败" value="0" /> | ||||
|           <el-option label="成功" :value="1" /> | ||||
|           <el-option label="失败" :value="0" /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="所属部门:" prop="department_id"> | ||||
|         <el-cascader | ||||
|           v-model="form.department_id" | ||||
|           class="!w-[200px]" | ||||
|           :options="departments" | ||||
|           :props="{ | ||||
|             value: 'id', | ||||
|             label: 'name', | ||||
|             emitPath: false, | ||||
|             checkStrictly: true | ||||
|           }" | ||||
|           clearable | ||||
|           filterable | ||||
|           placeholder="请选择所属部门~" | ||||
|         > | ||||
|           <template #default="{ node, data }"> | ||||
|             <span>{{ data.name }}</span> | ||||
|             <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span> | ||||
|           </template> | ||||
|         </el-cascader> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="登录时间" prop="loginTime"> | ||||
|         <el-date-picker | ||||
|           v-model="form.loginTime" | ||||
| @ -69,6 +102,8 @@ const { | ||||
|           range-separator="至" | ||||
|           start-placeholder="开始日期时间" | ||||
|           end-placeholder="结束日期时间" | ||||
|           value-format="x" | ||||
|           unlink-panels | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
| @ -86,16 +121,7 @@ const { | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
| 
 | ||||
|     <PureTableBar title="登录日志" :columns="columns" @refresh="onSearch"> | ||||
|       <!-- <template #buttons> | ||||
|         <el-popconfirm title="确定要删除所有日志数据吗?" @confirm="clearAll"> | ||||
|           <template #reference> | ||||
|             <el-button type="danger" :icon="useRenderIcon(Delete)"> | ||||
|               清空日志 | ||||
|             </el-button> | ||||
|           </template> | ||||
|         </el-popconfirm> | ||||
|       </template> --> | ||||
|     <PureTableBar title="登录日志管理" :columns="columns" @refresh="onSearch"> | ||||
|       <template v-slot="{ size, dynamicColumns }"> | ||||
|         <div | ||||
|           v-if="selectedNum > 0" | ||||
| @ -113,9 +139,34 @@ const { | ||||
|               {{ t("buttons:Deselect") }} | ||||
|             </el-button> | ||||
|           </div> | ||||
|           <el-popconfirm title="是否确认删除?" @confirm="onbatchDel"> | ||||
|           <el-popconfirm | ||||
|             v-if="hasAuth('login:btn:logout')" | ||||
|             title="是否确认强制退出?" | ||||
|             @confirm="onbatchForce" | ||||
|           > | ||||
|             <template #reference> | ||||
|               <el-button type="danger" text class="mr-1"> | ||||
|               <el-button | ||||
|                 type="warning" | ||||
|                 text | ||||
|                 class="mr-1" | ||||
|                 :disabled="selectedNum < 0 || !hasAuth('login:btn:logout')" | ||||
|               > | ||||
|                 {{ t("buttons:ExitInBatches") }} | ||||
|               </el-button> | ||||
|             </template> | ||||
|           </el-popconfirm> | ||||
|           <el-popconfirm | ||||
|             v-if="hasAuth('login:btn:delete')" | ||||
|             title="是否确认删除?" | ||||
|             @confirm="onbatchDel" | ||||
|           > | ||||
|             <template #reference> | ||||
|               <el-button | ||||
|                 type="danger" | ||||
|                 text | ||||
|                 class="mr-1" | ||||
|                 :disabled="selectedNum < 0 || !hasAuth('login:btn:delete')" | ||||
|               > | ||||
|                 {{ t("buttons:DeleteInBatches") }} | ||||
|               </el-button> | ||||
|             </template> | ||||
| @ -142,6 +193,23 @@ const { | ||||
|           @page-current-change="handleCurrentChange" | ||||
|         > | ||||
|           <template #operation="{ row }"> | ||||
|             <el-popconfirm | ||||
|               :title="`是否删除这条登录记录?`" | ||||
|               @confirm="handleDelete(row)" | ||||
|             > | ||||
|               <template #reference> | ||||
|                 <el-button | ||||
|                   class="reset-margin" | ||||
|                   link | ||||
|                   type="danger" | ||||
|                   :disabled="!hasAuth('login:btn:delete')" | ||||
|                   :size="size" | ||||
|                   :icon="useRenderIcon(Delete)" | ||||
|                 > | ||||
|                   {{ t("buttons:Delete") }} | ||||
|                 </el-button> | ||||
|               </template> | ||||
|             </el-popconfirm> | ||||
|             <el-popconfirm | ||||
|               :title="`是否强制下线${row.username}`" | ||||
|               @confirm="deleteUserHandle(row.session_id)" | ||||
| @ -151,7 +219,7 @@ const { | ||||
|                   class="reset-margin" | ||||
|                   link | ||||
|                   type="primary" | ||||
|                   :disabled="!row.online" | ||||
|                   :disabled="!row.online || !hasAuth('login:btn:logout')" | ||||
|                   :size="size" | ||||
|                   :icon="useRenderIcon(Plane)" | ||||
|                 > | ||||
|  | ||||
| @ -1,17 +1,27 @@ | ||||
| import dayjs from "dayjs"; | ||||
| import { message } from "@/utils/message"; | ||||
| import { getKeyList } from "@pureadmin/utils"; | ||||
| import { deleteUserOnlineAPI, getUserLoginLogAPI } from "@/api/monitor"; | ||||
| import { getKeyList, handleTree } from "@pureadmin/utils"; | ||||
| import { | ||||
|   deleteUserOnlineAPI, | ||||
|   getUserLoginLogAPI, | ||||
|   deleteUserLoginLogAPI, | ||||
|   deleteUserOnlineListAPI, | ||||
|   deleteUserLoginLogListAPI | ||||
| } from "@/api/monitor"; | ||||
| import { usePublicHooks } from "@/views/system/hooks"; | ||||
| import type { PaginationProps } from "@pureadmin/table"; | ||||
| import { type Ref, reactive, ref, onMounted } from "vue"; | ||||
| import type { UserLoginLogInfo } from "types/monitor"; | ||||
| import type { DepartmentInfo } from "types/system"; | ||||
| import { getDepartmentListAPI } from "@/api/system"; | ||||
| 
 | ||||
| export const useLogin = (tableRef: Ref) => { | ||||
|   const form = reactive({ | ||||
|     username: "", | ||||
|     nickname: "", | ||||
|     status: "", | ||||
|     loginTime: "" | ||||
|     department_id: "", | ||||
|     loginTime: [] | ||||
|   }); | ||||
|   /** | ||||
|    * 表格数据 | ||||
| @ -93,6 +103,16 @@ export const useLogin = (tableRef: Ref) => { | ||||
|         </el-tag> | ||||
|       ) | ||||
|     }, | ||||
|     { | ||||
|       label: "在线状态", | ||||
|       prop: "online", | ||||
|       minWidth: 100, | ||||
|       cellRenderer: ({ row, props }) => ( | ||||
|         <el-tag size={props.size} type={row.online ? "success" : ""}> | ||||
|           {row.online ? "在线" : "离线"} | ||||
|         </el-tag> | ||||
|       ) | ||||
|     }, | ||||
|     { | ||||
|       label: "登录时间", | ||||
|       prop: "login_time", | ||||
| @ -103,7 +123,8 @@ export const useLogin = (tableRef: Ref) => { | ||||
|     { | ||||
|       label: "操作", | ||||
|       fixed: "right", | ||||
|       slot: "operation" | ||||
|       slot: "operation", | ||||
|       width: 200 | ||||
|     } | ||||
|   ]; | ||||
| 
 | ||||
| @ -114,12 +135,19 @@ export const useLogin = (tableRef: Ref) => { | ||||
|   const handleCurrentChange = async (val: number) => { | ||||
|     const res = await getUserLoginLogAPI({ | ||||
|       page: val, | ||||
|       pageSize: pagination.pageSize | ||||
|       pageSize: pagination.pageSize, | ||||
|       department_id: form.department_id, | ||||
|       status: form.status, | ||||
|       username: form.username, | ||||
|       nickname: form.nickname, | ||||
|       startTime: form.loginTime[0] ? form.loginTime[0] : null, | ||||
|       endTime: form.loginTime[1] ? form.loginTime[1] : null | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|       pagination.pageSize = res.data.pageSize; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
| @ -127,12 +155,19 @@ export const useLogin = (tableRef: Ref) => { | ||||
|   const handleSizeChange = async (val: number) => { | ||||
|     const res = await getUserLoginLogAPI({ | ||||
|       page: pagination.currentPage, | ||||
|       pageSize: val | ||||
|       pageSize: val, | ||||
|       department_id: form.department_id, | ||||
|       status: form.status, | ||||
|       username: form.username, | ||||
|       nickname: form.nickname, | ||||
|       startTime: form.loginTime[0] ? form.loginTime[0] : null, | ||||
|       endTime: form.loginTime[1] ? form.loginTime[1] : null | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|       pagination.pageSize = res.data.pageSize; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
| @ -149,38 +184,77 @@ export const useLogin = (tableRef: Ref) => { | ||||
|     // 用于多选表格,清空用户的选择
 | ||||
|     tableRef.value.getTableRef().clearSelection(); | ||||
|   }; | ||||
| 
 | ||||
|   /**处理删除 */ | ||||
|   /** | ||||
|    * 处理删除 | ||||
|    * @param row | ||||
|    */ | ||||
|   const handleDelete = async (row: UserLoginLogInfo) => { | ||||
|     const res = await deleteUserLoginLogAPI(row.id); | ||||
|     if (res.success) { | ||||
|       onSearch(); | ||||
|     } | ||||
|     message(res.msg, { | ||||
|       type: res.success ? "success" : "error" | ||||
|     }); | ||||
|   }; | ||||
|   /** 批量删除 */ | ||||
|   const onbatchDel = () => { | ||||
|   const onbatchDel = async () => { | ||||
|     // 返回当前选中的行
 | ||||
|     const curSelected = tableRef.value.getTableRef().getSelectionRows(); | ||||
|     // 接下来根据实际业务,通过选中行的某项数据,比如下面的id,调用接口进行批量删除
 | ||||
|     message(`已删除序号为 ${getKeyList(curSelected, "id")} 的数据`, { | ||||
|     const res = await deleteUserLoginLogListAPI({ | ||||
|       ids: getKeyList(curSelected, "id") | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       message(res.msg, { | ||||
|         type: "success" | ||||
|       }); | ||||
|       tableRef.value.getTableRef().clearSelection(); | ||||
|       onSearch(); | ||||
|     } else { | ||||
|       message(res.msg, { | ||||
|         type: "error" | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /** 清空日志 */ | ||||
|   const clearAll = async () => { | ||||
|     // 根据实际业务,调用接口删除所有日志数据
 | ||||
|     message("已删除所有日志数据", { | ||||
|   /**批量强退 */ | ||||
|   const onbatchForce = async () => { | ||||
|     // 返回当前选中的行
 | ||||
|     const curSelected = tableRef.value.getTableRef().getSelectionRows(); | ||||
|     const res = await deleteUserOnlineListAPI({ | ||||
|       ids: getKeyList(curSelected, "id") | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       message(res.msg, { | ||||
|         type: "success" | ||||
|       }); | ||||
|     await onSearch(); | ||||
|       tableRef.value.getTableRef().clearSelection(); | ||||
|       onSearch(); | ||||
|     } else { | ||||
|       message(res.msg, { | ||||
|         type: "error" | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const onSearch = async () => { | ||||
|     loading.value = true; | ||||
|     const res = await getUserLoginLogAPI({ | ||||
|       page: pagination.currentPage, | ||||
|       pageSize: pagination.pageSize | ||||
|       pageSize: pagination.pageSize, | ||||
|       department_id: form.department_id, | ||||
|       status: form.status, | ||||
|       username: form.username, | ||||
|       nickname: form.nickname, | ||||
|       startTime: form.loginTime[0] ? form.loginTime[0] : null, | ||||
|       endTime: form.loginTime[1] ? form.loginTime[1] : null | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|       pagination.pageSize = res.data.pageSize; | ||||
|     } | ||||
|     setTimeout(() => { | ||||
|       loading.value = false; | ||||
| @ -201,9 +275,34 @@ export const useLogin = (tableRef: Ref) => { | ||||
|       onSearch(); | ||||
|     } | ||||
|   }; | ||||
|   /**部门列表 */ | ||||
|   const departments = ref<DepartmentInfo[]>([]); | ||||
|   /**获取部门列表 */ | ||||
|   const getDepartments = async () => { | ||||
|     const res = await getDepartmentListAPI({ page: 1, pageSize: 9999 }); | ||||
|     if (res.success) { | ||||
|       departments.value = formatHigherOptions( | ||||
|         handleTree(res.data.result, "id", "parent_id") | ||||
|       ); | ||||
|     } else { | ||||
|       departments.value = []; | ||||
|     } | ||||
|   }; | ||||
|   const formatHigherOptions = (treeList: any) => { | ||||
|     // 根据返回数据的status字段值判断追加是否禁用disabled字段,返回处理后的树结构,用于上级部门级联选择器的展示(实际开发中也是如此,不可能前端需要的每个字段后端都会返回,这时需要前端自行根据后端返回的某些字段做逻辑处理)
 | ||||
|     if (!treeList || !treeList.length) return; | ||||
|     const newTreeList = []; | ||||
|     for (let i = 0; i < treeList.length; i++) { | ||||
|       treeList[i].disabled = treeList[i].status === 0 ? true : false; | ||||
|       formatHigherOptions(treeList[i].children); | ||||
|       newTreeList.push(treeList[i]); | ||||
|     } | ||||
|     return newTreeList; | ||||
|   }; | ||||
| 
 | ||||
|   onMounted(() => { | ||||
|     onSearch(); | ||||
|   onMounted(async () => { | ||||
|     await onSearch(); | ||||
|     await getDepartments(); | ||||
|   }); | ||||
| 
 | ||||
|   return { | ||||
| @ -213,14 +312,16 @@ export const useLogin = (tableRef: Ref) => { | ||||
|     dataList, | ||||
|     pagination, | ||||
|     selectedNum, | ||||
|     departments, | ||||
|     onSearch, | ||||
|     clearAll, | ||||
|     resetForm, | ||||
|     onbatchDel, | ||||
|     onbatchForce, | ||||
|     resetForm, | ||||
|     deleteUserHandle, | ||||
|     onSelectionCancel, | ||||
|     handleCurrentChange, | ||||
|     handleSizeChange, | ||||
|     handleDelete, | ||||
|     handleSelectionChange | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| <script setup lang="ts"> | ||||
| import dayjs from "dayjs"; | ||||
| import { ref, reactive, computed } from "vue"; | ||||
| import { useOperation } from "./hook"; | ||||
| import { useOperation } from "./utils/hook"; | ||||
| import { getPickerShortcuts } from "../utils"; | ||||
| import { PureTableBar } from "@/components/RePureTableBar"; | ||||
| import { useRenderIcon } from "@/components/ReIcon/src/hooks"; | ||||
| @ -12,7 +12,7 @@ import View from "@iconify-icons/ep/view"; | ||||
| import Delete from "@iconify-icons/ep/delete"; | ||||
| import Refresh from "@iconify-icons/ep/refresh"; | ||||
| import { OperationLogInfo } from "types/monitor"; | ||||
| 
 | ||||
| import { hasAuth } from "@/utils/auth"; | ||||
| defineOptions({ | ||||
|   name: "OperationLog" | ||||
| }); | ||||
| @ -86,11 +86,12 @@ const { | ||||
|   dataList, | ||||
|   pagination, | ||||
|   selectedNum, | ||||
|   departments, | ||||
|   onSearch, | ||||
|   clearAll, | ||||
|   resetForm, | ||||
|   onbatchDel, | ||||
|   getOperationName, | ||||
|   handleDelete, | ||||
|   handleSizeChange, | ||||
|   onSelectionCancel, | ||||
|   handleCurrentChange, | ||||
| @ -106,33 +107,89 @@ const { | ||||
|       :model="form" | ||||
|       class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto" | ||||
|     > | ||||
|       <el-form-item label="所属模块" prop="module"> | ||||
|       <el-form-item label="操作名称" prop="name"> | ||||
|         <el-input | ||||
|           v-model="form.module" | ||||
|           placeholder="请输入所属模块" | ||||
|           v-model="form.name" | ||||
|           placeholder="请输入操作名称" | ||||
|           clearable | ||||
|           class="!w-[170px]" | ||||
|           class="!w-[200px]" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="操作类型" prop="type"> | ||||
|         <el-select | ||||
|           v-model="form.type" | ||||
|           placeholder="请选择操作类型~" | ||||
|           clearable | ||||
|           class="!w-[200px]" | ||||
|         > | ||||
|           <el-option label="查询" :value="1" /> | ||||
|           <el-option label="新增" :value="2" /> | ||||
|           <el-option label="修改" :value="3" /> | ||||
|           <el-option label="删除" :value="4" /> | ||||
|           <el-option label="授权" :value="5" /> | ||||
|           <el-option label="导出" :value="6" /> | ||||
|           <el-option label="导入" :value="7" /> | ||||
|           <el-option label="强退" :value="8" /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="用户账号" prop="username"> | ||||
|         <el-input | ||||
|           v-model="form.username" | ||||
|           placeholder="请输入用户账号~" | ||||
|           clearable | ||||
|           class="!w-[200px]" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="用户名称" prop="nickname"> | ||||
|         <el-input | ||||
|           v-model="form.nickname" | ||||
|           placeholder="请输入用户名称~" | ||||
|           clearable | ||||
|           class="!w-[200px]" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="操作状态" prop="status"> | ||||
|         <el-select | ||||
|           v-model="form.status" | ||||
|           placeholder="请选择" | ||||
|           placeholder="请选择操作状态~" | ||||
|           clearable | ||||
|           class="!w-[150px]" | ||||
|           class="!w-[200px]" | ||||
|         > | ||||
|           <el-option label="成功" value="1" /> | ||||
|           <el-option label="失败" value="0" /> | ||||
|           <el-option label="成功" :value="1" /> | ||||
|           <el-option label="失败" :value="0" /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="所属部门:" prop="department_id"> | ||||
|         <el-cascader | ||||
|           v-model="form.department_id" | ||||
|           class="!w-[200px]" | ||||
|           :options="departments" | ||||
|           :props="{ | ||||
|             value: 'id', | ||||
|             label: 'name', | ||||
|             emitPath: false, | ||||
|             checkStrictly: true | ||||
|           }" | ||||
|           clearable | ||||
|           filterable | ||||
|           placeholder="请选择所属部门~" | ||||
|         > | ||||
|           <template #default="{ node, data }"> | ||||
|             <span>{{ data.name }}</span> | ||||
|             <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span> | ||||
|           </template> | ||||
|         </el-cascader> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="操作时间" prop="operatingTime"> | ||||
|         <el-date-picker | ||||
|           v-model="form.operatingTime" | ||||
|           v-model="form.operationTime" | ||||
|           :shortcuts="getPickerShortcuts()" | ||||
|           type="datetimerange" | ||||
|           range-separator="至" | ||||
|           start-placeholder="开始日期时间" | ||||
|           end-placeholder="结束日期时间" | ||||
|           value-format="x" | ||||
|           unlink-panels | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
| @ -151,15 +208,6 @@ const { | ||||
|     </el-form> | ||||
| 
 | ||||
|     <PureTableBar title="操作日志" :columns="columns" @refresh="onSearch"> | ||||
|       <template #buttons> | ||||
|         <!-- <el-popconfirm title="确定要删除所有日志数据吗?" @confirm="clearAll"> | ||||
|           <template #reference> | ||||
|             <el-button type="danger" :icon="useRenderIcon(Delete)"> | ||||
|               清空日志 | ||||
|             </el-button> | ||||
|           </template> | ||||
|         </el-popconfirm> --> | ||||
|       </template> | ||||
|       <template v-slot="{ size, dynamicColumns }"> | ||||
|         <div | ||||
|           v-if="selectedNum > 0" | ||||
| @ -177,9 +225,18 @@ const { | ||||
|               {{ t("buttons:Deselect") }} | ||||
|             </el-button> | ||||
|           </div> | ||||
|           <el-popconfirm title="是否确认删除?" @confirm="onbatchDel"> | ||||
|           <el-popconfirm | ||||
|             v-if="hasAuth('operation:btn:delete')" | ||||
|             title="是否确认删除?" | ||||
|             @confirm="onbatchDel" | ||||
|           > | ||||
|             <template #reference> | ||||
|               <el-button type="danger" text class="mr-1"> | ||||
|               <el-button | ||||
|                 type="danger" | ||||
|                 text | ||||
|                 class="mr-1" | ||||
|                 :disabled="selectedNum < 0 || !hasAuth('operation:btn:delete')" | ||||
|               > | ||||
|                 {{ t("buttons:DeleteInBatches") }} | ||||
|               </el-button> | ||||
|             </template> | ||||
| @ -211,11 +268,29 @@ const { | ||||
|               link | ||||
|               type="primary" | ||||
|               :size="size" | ||||
|               :disabled="!hasAuth('operation:btn:info')" | ||||
|               :icon="useRenderIcon(View)" | ||||
|               @click="onDetailHandle(row)" | ||||
|             > | ||||
|               {{ t("buttons:Details") }} | ||||
|             </el-button> | ||||
|             <el-popconfirm | ||||
|               :title="`是否删除这条操作记录?`" | ||||
|               @confirm="handleDelete(row)" | ||||
|             > | ||||
|               <template #reference> | ||||
|                 <el-button | ||||
|                   class="reset-margin" | ||||
|                   link | ||||
|                   type="danger" | ||||
|                   :disabled="!hasAuth('operation:btn:delete')" | ||||
|                   :size="size" | ||||
|                   :icon="useRenderIcon(Delete)" | ||||
|                 > | ||||
|                   {{ t("buttons:Delete") }} | ||||
|                 </el-button> | ||||
|               </template> | ||||
|             </el-popconfirm> | ||||
|           </template> | ||||
|         </pure-table> | ||||
|       </template> | ||||
|  | ||||
							
								
								
									
										384
									
								
								src/views/monitor/operation/utils/hook.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										384
									
								
								src/views/monitor/operation/utils/hook.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,384 @@ | ||||
| import dayjs from "dayjs"; | ||||
| // import Detail from "../components/form.vue";
 | ||||
| import { message } from "@/utils/message"; | ||||
| // import { addDialog } from "@/components/ReDialog";
 | ||||
| import { getKeyList, handleTree } from "@pureadmin/utils"; | ||||
| import { | ||||
|   getUserOperationsAPI, | ||||
|   deleteUserOperationsAPI, | ||||
|   deleteUserOperationsListAPI | ||||
| } from "@/api/monitor"; | ||||
| import { usePublicHooks } from "@/views/system/hooks"; | ||||
| import type { PaginationProps } from "@pureadmin/table"; | ||||
| import { type Ref, reactive, ref, onMounted } from "vue"; | ||||
| import type { OperationLogInfo } from "types/monitor"; | ||||
| import type { DepartmentInfo } from "types/system"; | ||||
| import { getDepartmentListAPI } from "@/api/system"; | ||||
| 
 | ||||
| export function useOperation(tableRef: Ref) { | ||||
|   const form = reactive({ | ||||
|     name: "", | ||||
|     type: "", | ||||
|     username: "", | ||||
|     nickname: "", | ||||
|     status: "", | ||||
|     department_id: "", | ||||
|     operationTime: "" | ||||
|   }); | ||||
|   const dataList = ref<OperationLogInfo[]>([]); | ||||
|   const loading = ref(true); | ||||
|   const selectedNum = ref(0); | ||||
|   const { tagStyle } = usePublicHooks(); | ||||
| 
 | ||||
|   const pagination = reactive<PaginationProps>({ | ||||
|     total: 0, | ||||
|     pageSize: 10, | ||||
|     currentPage: 1, | ||||
|     background: true, | ||||
|     pageSizes: [10, 20, 30, 40, 50] | ||||
|   }); | ||||
| 
 | ||||
|   const getOperationType = (type: number) => { | ||||
|     switch (type) { | ||||
|       case 1: | ||||
|         return "primary"; | ||||
|       case 2: | ||||
|         return "success"; | ||||
|       case 3: | ||||
|         return "warning"; | ||||
|       case 4: | ||||
|         return "danger"; | ||||
|       case 5: | ||||
|         return "success"; | ||||
|       case 6: | ||||
|         return "primary"; | ||||
|       case 7: | ||||
|         return "success"; | ||||
|       case 8: | ||||
|         return "danger"; | ||||
|       default: | ||||
|         return "info"; | ||||
|     } | ||||
|   }; | ||||
|   const getOperationName = (type: number) => { | ||||
|     switch (type) { | ||||
|       case 1: | ||||
|         return "查询"; | ||||
|       case 2: | ||||
|         return "新增"; | ||||
|       case 3: | ||||
|         return "修改"; | ||||
|       case 4: | ||||
|         return "删除"; | ||||
|       case 5: | ||||
|         return "授权"; | ||||
|       case 6: | ||||
|         return "导出"; | ||||
|       case 7: | ||||
|         return "导入"; | ||||
|       case 8: | ||||
|         return "强退"; | ||||
|       default: | ||||
|         return "其他"; | ||||
|     } | ||||
|   }; | ||||
|   const getRequestType = (method: string) => { | ||||
|     switch (method) { | ||||
|       case "GET": | ||||
|         return "primary"; | ||||
|       case "POST": | ||||
|         return "success"; | ||||
|       case "PUT": | ||||
|         return "warning"; | ||||
|       case "DELETE": | ||||
|         return "danger"; | ||||
|       case "PATCH": | ||||
|         return "info"; | ||||
|       default: | ||||
|         return "info"; | ||||
|     } | ||||
|   }; | ||||
|   const columns: TableColumnList = [ | ||||
|     { | ||||
|       label: "勾选列", // 如果需要表格多选,此处label必须设置
 | ||||
|       type: "selection", | ||||
|       fixed: "left", | ||||
|       reserveSelection: true // 数据刷新后保留选项
 | ||||
|     }, | ||||
|     { | ||||
|       label: "操作人员账号", | ||||
|       prop: "operator_name" | ||||
|     }, | ||||
|     { | ||||
|       label: "操作人员昵称", | ||||
|       prop: "operator_nickname" | ||||
|     }, | ||||
|     { | ||||
|       label: "所属部门", | ||||
|       prop: "department_name" | ||||
|     }, | ||||
|     { | ||||
|       label: "操作名称", | ||||
|       prop: "operation_name" | ||||
|     }, | ||||
|     { | ||||
|       label: "操作类型", | ||||
|       prop: "operation_type", | ||||
|       cellRenderer: ({ row, props }) => ( | ||||
|         <el-tag size={props.size} type={getOperationType(row.operation_type)}> | ||||
|           {getOperationName(row.operation_type)} | ||||
|         </el-tag> | ||||
|       ) | ||||
|     }, | ||||
|     { | ||||
|       label: "操作IP", | ||||
|       prop: "host" | ||||
|     }, | ||||
|     { | ||||
|       label: "操作地点", | ||||
|       prop: "location" | ||||
|     }, | ||||
|     { | ||||
|       label: "请求方法", | ||||
|       prop: "request_method", | ||||
|       cellRenderer: ({ row, props }) => ( | ||||
|         <el-tag size={props.size} type={getRequestType(row.request_method)}> | ||||
|           {row.request_method} | ||||
|         </el-tag> | ||||
|       ) | ||||
|     }, | ||||
|     { | ||||
|       label: "请求路径", | ||||
|       prop: "request_path" | ||||
|     }, | ||||
|     { | ||||
|       label: "请求耗时", | ||||
|       prop: "cost_time", | ||||
|       cellRenderer: ({ row, props }) => ( | ||||
|         <el-tag | ||||
|           size={props.size} | ||||
|           type={row.cost_time < 1000 ? "success" : "warning"} | ||||
|           effect="plain" | ||||
|         > | ||||
|           {row.cost_time.toFixed(2)} ms | ||||
|         </el-tag> | ||||
|       ) | ||||
|     }, | ||||
|     { | ||||
|       label: "操作系统", | ||||
|       prop: "os", | ||||
|       hide: true | ||||
|     }, | ||||
|     { | ||||
|       label: "浏览器类型", | ||||
|       prop: "browser", | ||||
|       hide: true | ||||
|     }, | ||||
|     { | ||||
|       label: "操作状态", | ||||
|       prop: "status", | ||||
|       cellRenderer: ({ row, props }) => ( | ||||
|         <el-tag size={props.size} style={tagStyle.value(row.status)}> | ||||
|           {row.status === 1 ? "成功" : "失败"} | ||||
|         </el-tag> | ||||
|       ) | ||||
|     }, | ||||
|     { | ||||
|       label: "操作时间", | ||||
|       prop: "operation_time", | ||||
|       minWidth: 180, | ||||
|       formatter: ({ operation_time }) => | ||||
|         dayjs(operation_time).format("YYYY-MM-DD HH:mm:ss") | ||||
|     }, | ||||
|     { | ||||
|       label: "操作", | ||||
|       fixed: "right", | ||||
|       width: 180, | ||||
|       slot: "operation" | ||||
|     } | ||||
|   ]; | ||||
| 
 | ||||
|   const handleSizeChange = async (val: number) => { | ||||
|     const res = await getUserOperationsAPI({ | ||||
|       page: pagination.currentPage, | ||||
|       pageSize: val, | ||||
|       name: form.name, | ||||
|       type: form.type, | ||||
|       username: form.username, | ||||
|       nickname: form.nickname, | ||||
|       status: form.status, | ||||
|       department_id: form.department_id, | ||||
|       startTime: form.operationTime[0] ? form.operationTime[0] : null, | ||||
|       endTime: form.operationTime[1] ? form.operationTime[1] : null | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|       pagination.pageSize = res.data.pageSize; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const handleCurrentChange = async (val: number) => { | ||||
|     const res = await getUserOperationsAPI({ | ||||
|       page: val, | ||||
|       pageSize: pagination.pageSize, | ||||
|       name: form.name, | ||||
|       type: form.type, | ||||
|       username: form.username, | ||||
|       nickname: form.nickname, | ||||
|       status: form.status, | ||||
|       department_id: form.department_id, | ||||
|       startTime: form.operationTime[0] ? form.operationTime[0] : null, | ||||
|       endTime: form.operationTime[1] ? form.operationTime[1] : null | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|       pagination.pageSize = res.data.pageSize; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /** 当CheckBox选择项发生变化时会触发该事件 */ | ||||
|   const handleSelectionChange = val => { | ||||
|     selectedNum.value = val.length; | ||||
|     // 重置表格高度
 | ||||
|     tableRef.value.setAdaptive(); | ||||
|   }; | ||||
| 
 | ||||
|   /** 取消选择 */ | ||||
|   const onSelectionCancel = () => { | ||||
|     selectedNum.value = 0; | ||||
|     // 用于多选表格,清空用户的选择
 | ||||
|     tableRef.value.getTableRef().clearSelection(); | ||||
|   }; | ||||
|   /** | ||||
|    * 处理删除 | ||||
|    * @param row | ||||
|    */ | ||||
|   const handleDelete = async (row: OperationLogInfo) => { | ||||
|     const res = await deleteUserOperationsAPI(row.id); | ||||
|     if (res.success) { | ||||
|       onSearch(); | ||||
|     } | ||||
|     message(res.msg, { | ||||
|       type: res.success ? "success" : "error" | ||||
|     }); | ||||
|   }; | ||||
|   /** 批量删除 */ | ||||
|   const onbatchDel = async () => { | ||||
|     // 返回当前选中的行
 | ||||
|     const curSelected = tableRef.value.getTableRef().getSelectionRows(); | ||||
|     const res = await deleteUserOperationsListAPI({ | ||||
|       ids: getKeyList(curSelected, "id") | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       message(res.msg, { | ||||
|         type: "success" | ||||
|       }); | ||||
|       tableRef.value.getTableRef().clearSelection(); | ||||
|       onSearch(); | ||||
|     } else { | ||||
|       message(res.msg, { | ||||
|         type: "error" | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const onSearch = async () => { | ||||
|     loading.value = true; | ||||
|     const res = await getUserOperationsAPI({ | ||||
|       page: pagination.currentPage, | ||||
|       pageSize: pagination.pageSize, | ||||
|       name: form.name, | ||||
|       type: form.type, | ||||
|       username: form.username, | ||||
|       nickname: form.nickname, | ||||
|       status: form.status, | ||||
|       department_id: form.department_id, | ||||
|       startTime: form.operationTime[0] ? form.operationTime[0] : null, | ||||
|       endTime: form.operationTime[1] ? form.operationTime[1] : null | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|       pagination.pageSize = res.data.pageSize; | ||||
|     } | ||||
| 
 | ||||
|     setTimeout(() => { | ||||
|       loading.value = false; | ||||
|     }, 500); | ||||
|   }; | ||||
| 
 | ||||
|   const resetForm = formEl => { | ||||
|     if (!formEl) return; | ||||
|     formEl.resetFields(); | ||||
|     onSearch(); | ||||
|   }; | ||||
| 
 | ||||
|   // const onDetailHandle = () => {
 | ||||
|   //   addDialog({
 | ||||
|   //     title: `详情`,
 | ||||
|   //     props: {},
 | ||||
|   //     width: "40%",
 | ||||
|   //     draggable: true,
 | ||||
|   //     fullscreenIcon: true,
 | ||||
|   //     closeOnClickModal: false,
 | ||||
|   //     contentRenderer: () => h(Detail, {}),
 | ||||
|   //     beforeSure: (done, {}) => {
 | ||||
|   //       done();
 | ||||
|   //     }
 | ||||
|   //   });
 | ||||
|   // };
 | ||||
| 
 | ||||
|   /**部门列表 */ | ||||
|   const departments = ref<DepartmentInfo[]>([]); | ||||
|   /**获取部门列表 */ | ||||
|   const getDepartments = async () => { | ||||
|     const res = await getDepartmentListAPI({ page: 1, pageSize: 9999 }); | ||||
|     if (res.success) { | ||||
|       departments.value = formatHigherOptions( | ||||
|         handleTree(res.data.result, "id", "parent_id") | ||||
|       ); | ||||
|     } else { | ||||
|       departments.value = []; | ||||
|     } | ||||
|   }; | ||||
|   const formatHigherOptions = (treeList: any) => { | ||||
|     // 根据返回数据的status字段值判断追加是否禁用disabled字段,返回处理后的树结构,用于上级部门级联选择器的展示(实际开发中也是如此,不可能前端需要的每个字段后端都会返回,这时需要前端自行根据后端返回的某些字段做逻辑处理)
 | ||||
|     if (!treeList || !treeList.length) return; | ||||
|     const newTreeList = []; | ||||
|     for (let i = 0; i < treeList.length; i++) { | ||||
|       treeList[i].disabled = treeList[i].status === 0 ? true : false; | ||||
|       formatHigherOptions(treeList[i].children); | ||||
|       newTreeList.push(treeList[i]); | ||||
|     } | ||||
|     return newTreeList; | ||||
|   }; | ||||
| 
 | ||||
|   onMounted(async () => { | ||||
|     await onSearch(); | ||||
|     await getDepartments(); | ||||
|   }); | ||||
| 
 | ||||
|   return { | ||||
|     form, | ||||
|     loading, | ||||
|     columns, | ||||
|     dataList, | ||||
|     pagination, | ||||
|     selectedNum, | ||||
|     departments, | ||||
|     onSearch, | ||||
|     resetForm, | ||||
|     onbatchDel, | ||||
|     getOperationName, | ||||
|     handleDelete, | ||||
|     handleSizeChange, | ||||
|     onSelectionCancel, | ||||
|     handleCurrentChange, | ||||
|     handleSelectionChange | ||||
|   }; | ||||
| } | ||||
| @ -45,6 +45,7 @@ | ||||
|     <PureTableBar title="配置管理" :columns="columns" @refresh="onSearch"> | ||||
|       <template #buttons> | ||||
|         <el-button | ||||
|           v-if="hasAuth('config:btn:add')" | ||||
|           type="primary" | ||||
|           :icon="useRenderIcon(AddFill)" | ||||
|           @click="openDialog('新增')" | ||||
| @ -69,7 +70,11 @@ | ||||
|               {{ t("buttons:Deselect") }} | ||||
|             </el-button> | ||||
|           </div> | ||||
|           <el-popconfirm title="是否确认删除?" @confirm="onbatchDel"> | ||||
|           <el-popconfirm | ||||
|             v-if="hasAuth('config:btn:delete')" | ||||
|             title="是否确认删除?" | ||||
|             @confirm="onbatchDel" | ||||
|           > | ||||
|             <template #reference> | ||||
|               <el-button type="danger" text class="mr-1"> | ||||
|                 {{ t("buttons:DeleteInBatches") }} | ||||
| @ -106,6 +111,7 @@ | ||||
|               link | ||||
|               type="primary" | ||||
|               :size="size" | ||||
|               :disabled="!hasAuth('config:btn:update')" | ||||
|               :icon="useRenderIcon(EditPen)" | ||||
|               @click="openDialog('修改', row)" | ||||
|             > | ||||
| @ -119,6 +125,7 @@ | ||||
|                 <el-button | ||||
|                   class="reset-margin" | ||||
|                   link | ||||
|                   :disabled="!hasAuth('config:btn:delete')" | ||||
|                   type="danger" | ||||
|                   :size="size" | ||||
|                   :icon="useRenderIcon(Delete)" | ||||
| @ -148,6 +155,7 @@ import EditPen from "@iconify-icons/ep/edit-pen"; | ||||
| import Refresh from "@iconify-icons/ep/refresh"; | ||||
| import AddFill from "@iconify-icons/ri/add-circle-line"; | ||||
| const { t } = useI18n(); | ||||
| import { hasAuth } from "@/utils/auth"; | ||||
| /** | ||||
|  * 表格Ref | ||||
|  */ | ||||
|  | ||||
| @ -202,7 +202,7 @@ export const useConfig = (tableRef: Ref) => { | ||||
|     const res = await deleteConfigListAPI({ | ||||
|       ids: getKeyList(curSelected, "id") | ||||
|     }); | ||||
|     if (res.code === 200) { | ||||
|     if (res.success) { | ||||
|       message(`已删除项目名称为 ${getKeyList(curSelected, "name")} 的数据`, { | ||||
|         type: "success" | ||||
|       }); | ||||
|  | ||||
| @ -9,6 +9,7 @@ import EditPen from "@iconify-icons/ep/edit-pen"; | ||||
| import Refresh from "@iconify-icons/ep/refresh"; | ||||
| import AddFill from "@iconify-icons/ri/add-circle-line"; | ||||
| import { onBeforeRouteUpdate } from "vue-router"; | ||||
| import { hasAuth } from "@/utils/auth"; | ||||
| 
 | ||||
| defineOptions({ | ||||
|   name: "SystemDepartment" | ||||
| @ -82,6 +83,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|     > | ||||
|       <template #buttons> | ||||
|         <el-button | ||||
|           v-if="hasAuth('department:btn:add')" | ||||
|           type="primary" | ||||
|           :icon="useRenderIcon(AddFill)" | ||||
|           @click="openDialog()" | ||||
| @ -117,6 +119,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|               link | ||||
|               type="primary" | ||||
|               :size="size" | ||||
|               :disabled="!hasAuth('department:btn:update')" | ||||
|               :icon="useRenderIcon(EditPen)" | ||||
|               @click="openDialog('修改', row)" | ||||
|             > | ||||
| @ -127,6 +130,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|               link | ||||
|               type="primary" | ||||
|               :size="size" | ||||
|               :disabled="!hasAuth('department:btn:add')" | ||||
|               :icon="useRenderIcon(AddFill)" | ||||
|               @click="openDialog('新增', { parent_id: row.id } as any)" | ||||
|             > | ||||
| @ -142,6 +146,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|                   link | ||||
|                   type="danger" | ||||
|                   :size="size" | ||||
|                   :disabled="!hasAuth('department:btn:delete')" | ||||
|                   :icon="useRenderIcon(Delete)" | ||||
|                 > | ||||
|                   {{ t("buttons:Delete") }} | ||||
|  | ||||
| @ -219,7 +219,7 @@ export const useDepartment = () => { | ||||
|    */ | ||||
|   const handleDelete = async (row: DepartmentInfo) => { | ||||
|     const res = await deleteDepartmentAPI(row.id); | ||||
|     if (res.code === 200) { | ||||
|     if (res.success) { | ||||
|       message(`您删除了部门:${row.name}及其附属部门`, { type: "success" }); | ||||
|       onSearch(); | ||||
|     } else { | ||||
|  | ||||
| @ -54,6 +54,7 @@ | ||||
|     <PureTableBar title="国际化管理" :columns="columns" @refresh="onSearch"> | ||||
|       <template #buttons> | ||||
|         <el-button | ||||
|           v-if="hasAuth('i18n:btn:add')" | ||||
|           type="primary" | ||||
|           :icon="useRenderIcon(AddFill)" | ||||
|           @click="openDialog('新增')" | ||||
| @ -78,9 +79,18 @@ | ||||
|               {{ t("buttons:Deselect") }} | ||||
|             </el-button> | ||||
|           </div> | ||||
|           <el-popconfirm title="是否确认删除?"> | ||||
|           <el-popconfirm | ||||
|             v-if="hasAuth('i18n:btn:delete')" | ||||
|             title="是否确认删除?" | ||||
|             @confirm="onbatchDel" | ||||
|           > | ||||
|             <template #reference> | ||||
|               <el-button type="danger" text class="mr-1"> | ||||
|               <el-button | ||||
|                 type="danger" | ||||
|                 text | ||||
|                 class="mr-1" | ||||
|                 :disabled="selectedNum < 0 || !hasAuth('i18n:btn:delete')" | ||||
|               > | ||||
|                 {{ t("buttons:DeleteInBatches") }} | ||||
|               </el-button> | ||||
|             </template> | ||||
| @ -115,6 +125,7 @@ | ||||
|               link | ||||
|               type="primary" | ||||
|               :size="size" | ||||
|               :disabled="!hasAuth('i18n:btn:update')" | ||||
|               :icon="useRenderIcon(EditPen)" | ||||
|               @click="openDialog('修改', row)" | ||||
|             > | ||||
| @ -130,6 +141,7 @@ | ||||
|                   link | ||||
|                   type="danger" | ||||
|                   :size="size" | ||||
|                   :disabled="!hasAuth('i18n:btn:delete')" | ||||
|                   :icon="useRenderIcon(Delete)" | ||||
|                 > | ||||
|                   {{ t("buttons:Delete") }} | ||||
| @ -148,7 +160,7 @@ defineOptions({ | ||||
|   name: "I18nIndex" | ||||
| }); | ||||
| import { ref } from "vue"; | ||||
| import { useI18n } from "./hook"; | ||||
| import { useI18n } from "./utils/hook"; | ||||
| import { useI18n as i18n } from "vue-i18n"; | ||||
| import { PureTableBar } from "@/components/RePureTableBar"; | ||||
| import { useRenderIcon } from "@/components/ReIcon/src/hooks"; | ||||
| @ -156,6 +168,7 @@ import Delete from "@iconify-icons/ep/delete"; | ||||
| import EditPen from "@iconify-icons/ep/edit-pen"; | ||||
| import Refresh from "@iconify-icons/ep/refresh"; | ||||
| import AddFill from "@iconify-icons/ri/add-circle-line"; | ||||
| import { hasAuth } from "@/utils/auth"; | ||||
| const { t } = i18n(); | ||||
| /** | ||||
|  * 表格Ref | ||||
| @ -177,7 +190,8 @@ const { | ||||
|   handleSizeChange, | ||||
|   handleCurrentChange, | ||||
|   handleSelectionChange, | ||||
|   onSelectionCancel | ||||
|   onSelectionCancel, | ||||
|   onbatchDel | ||||
| } = useI18n(tableRef); | ||||
| </script> | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										308
									
								
								src/views/system/i18n/utils/hook.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										308
									
								
								src/views/system/i18n/utils/hook.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,308 @@ | ||||
| import dayjs from "dayjs"; | ||||
| import editForm from "../components/form.vue"; | ||||
| import { message } from "@/utils/message"; | ||||
| import { type Ref, ref, reactive, onMounted, h } from "vue"; | ||||
| import type { LanguageInfo, TranslationInfo } from "types/system"; | ||||
| import type { PaginationProps } from "@pureadmin/table"; | ||||
| import { addDialog } from "@/components/ReDialog"; | ||||
| import { | ||||
|   deleteI18nAPI, | ||||
|   deleteI18nListAPI, | ||||
|   getI18nListAPI, | ||||
|   getLocaleListAPI, | ||||
|   postAddI18nAPI, | ||||
|   putUpdateI18nAPI | ||||
| } from "@/api/i18n"; | ||||
| import { getKeyList } from "@pureadmin/utils"; | ||||
| 
 | ||||
| export const useI18n = (tableRef: Ref) => { | ||||
|   /** | ||||
|    * 查询表单 | ||||
|    */ | ||||
|   const form = reactive({ | ||||
|     key: "", | ||||
|     locale_id: "", | ||||
|     translation: "" | ||||
|   }); | ||||
|   /** | ||||
|    * 表单Ref | ||||
|    */ | ||||
|   const formRef = ref(null); | ||||
|   /** | ||||
|    * 数据列表 | ||||
|    */ | ||||
|   const dataList = ref<TranslationInfo[]>([]); | ||||
|   /** | ||||
|    * 加载状态 | ||||
|    */ | ||||
|   const loading = ref(true); | ||||
|   /** | ||||
|    * 已选数量 | ||||
|    */ | ||||
|   const selectedNum = ref<number>(0); | ||||
|   /** | ||||
|    * 分页参数 | ||||
|    */ | ||||
|   const pagination = reactive<PaginationProps>({ | ||||
|     total: 0, | ||||
|     pageSize: 10, | ||||
|     currentPage: 1, | ||||
|     background: true, | ||||
|     pageSizes: [10, 20, 30, 40, 50] | ||||
|   }); | ||||
|   /** | ||||
|    * 表格列设置 | ||||
|    */ | ||||
|   const columns: TableColumnList = [ | ||||
|     { | ||||
|       label: "勾选列", // 如果需要表格多选,此处label必须设置
 | ||||
|       type: "selection", | ||||
|       fixed: "left", | ||||
|       reserveSelection: true // 数据刷新后保留选项
 | ||||
|     }, | ||||
|     { | ||||
|       label: "国际化key", | ||||
|       prop: "key" | ||||
|     }, | ||||
|     { | ||||
|       label: "国际化值", | ||||
|       prop: "translation" | ||||
|     }, | ||||
|     { | ||||
|       label: "语言编码", | ||||
|       prop: "locale_code" | ||||
|     }, | ||||
|     { | ||||
|       label: "语言名称", | ||||
|       prop: "locale_name" | ||||
|     }, | ||||
|     { | ||||
|       label: "创建时间", | ||||
|       prop: "create_time", | ||||
|       formatter: ({ create_time }) => | ||||
|         dayjs(create_time).format("YYYY-MM-DD HH:mm:ss") | ||||
|     }, | ||||
|     { | ||||
|       label: "操作", | ||||
|       fixed: "right", | ||||
|       width: 220, | ||||
|       slot: "operation" | ||||
|     } | ||||
|   ]; | ||||
| 
 | ||||
|   /** | ||||
|    * 初次查询 | ||||
|    */ | ||||
|   const onSearch = async () => { | ||||
|     loading.value = true; | ||||
|     const res = await getI18nListAPI({ | ||||
|       page: pagination.currentPage, | ||||
|       pageSize: pagination.pageSize, | ||||
|       key: form.key, | ||||
|       locale_id: form.locale_id, | ||||
|       translation: form.translation | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|     } | ||||
|     message(res.msg, { | ||||
|       type: res.success ? "success" : "error" | ||||
|     }); | ||||
|     loading.value = false; | ||||
|   }; | ||||
|   /** | ||||
|    * 重置表单 | ||||
|    * @param formEl 表单ref | ||||
|    * @returns | ||||
|    */ | ||||
|   const resetForm = (formEl: any) => { | ||||
|     if (!formEl) return; | ||||
|     formEl.resetFields(); | ||||
|     onSearch(); | ||||
|   }; | ||||
|   /** | ||||
|    * 处理删除 | ||||
|    * @param row | ||||
|    */ | ||||
|   const handleDelete = async (row: TranslationInfo) => { | ||||
|     const res = await deleteI18nAPI(row.id); | ||||
|     if (res.success) { | ||||
|       onSearch(); | ||||
|     } | ||||
|     message(res.msg, { | ||||
|       type: res.success ? "success" : "error" | ||||
|     }); | ||||
|   }; | ||||
|   /** | ||||
|    * 处理每页数量变化 | ||||
|    */ | ||||
|   const handleSizeChange = async (val: number) => { | ||||
|     const res = await getI18nListAPI({ | ||||
|       page: pagination.currentPage, | ||||
|       pageSize: val, | ||||
|       key: form.key, | ||||
|       locale_id: form.locale_id, | ||||
|       translation: form.translation | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|     } | ||||
|     message(res.msg, { | ||||
|       type: res.success ? "success" : "error" | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 处理页码变化 | ||||
|    * @param val | ||||
|    */ | ||||
|   const handleCurrentChange = async (val: number) => { | ||||
|     const res = await getI18nListAPI({ | ||||
|       page: val, | ||||
|       pageSize: pagination.pageSize, | ||||
|       key: form.key, | ||||
|       locale_id: form.locale_id, | ||||
|       translation: form.translation | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|     } | ||||
|     message(res.msg, { | ||||
|       type: res.success ? "success" : "error" | ||||
|     }); | ||||
|   }; | ||||
|   /** 当CheckBox选择项发生变化时会触发该事件 */ | ||||
|   const handleSelectionChange = async (val: any) => { | ||||
|     selectedNum.value = val.length; | ||||
|     // 重置表格高度
 | ||||
|     tableRef.value.setAdaptive(); | ||||
|   }; | ||||
| 
 | ||||
|   /** 取消选择 */ | ||||
|   const onSelectionCancel = async () => { | ||||
|     selectedNum.value = 0; | ||||
|     // 用于多选表格,清空用户的选择
 | ||||
|     tableRef.value.getTableRef().clearSelection(); | ||||
|   }; | ||||
|   /** | ||||
|    * 批量删除 | ||||
|    */ | ||||
|   const onbatchDel = async () => { | ||||
|     // 返回当前选中的行
 | ||||
|     const curSelected = tableRef.value.getTableRef().getSelectionRows(); | ||||
|     const res = await deleteI18nListAPI(getKeyList(curSelected, "id")); | ||||
|     if (res.success) { | ||||
|       message(res.msg, { | ||||
|         type: "success" | ||||
|       }); | ||||
|       tableRef.value.getTableRef().clearSelection(); | ||||
|       onSearch(); | ||||
|     } else { | ||||
|       message(res.msg, { type: "error", duration: 5000 }); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const openDialog = async (title = "新增", row?: TranslationInfo) => { | ||||
|     addDialog({ | ||||
|       title: `${title}国际化项`, | ||||
|       props: { | ||||
|         formInline: { | ||||
|           title: title, | ||||
|           key: row?.key ?? "", | ||||
|           locale_id: row?.locale_id ?? "", | ||||
|           translation: row?.translation ?? "" | ||||
|         } | ||||
|       }, | ||||
|       width: "45%", | ||||
|       draggable: true, | ||||
|       fullscreenIcon: true, | ||||
|       closeOnClickModal: false, | ||||
|       contentRenderer: () => | ||||
|         h(editForm, { | ||||
|           formInline: { | ||||
|             title: title, | ||||
|             key: row?.key ?? "", | ||||
|             locale_id: row?.locale_id ?? "", | ||||
|             translation: row?.translation ?? "" | ||||
|           }, | ||||
|           ref: formRef | ||||
|         }), | ||||
|       beforeSure: async (done, {}) => { | ||||
|         const FormData = formRef.value.newFormInline; | ||||
|         let form = { | ||||
|           key: FormData.key ?? "", | ||||
|           locale_id: FormData.locale_id ?? "", | ||||
|           translation: FormData.translation ?? "" | ||||
|         }; | ||||
|         if (title === "新增") { | ||||
|           const res = await postAddI18nAPI(form); | ||||
|           if (res.success) { | ||||
|             done(); | ||||
|             await onSearch(); | ||||
|           } | ||||
|           message(res.msg, { type: res.success ? "success" : "error" }); | ||||
|         } else { | ||||
|           const res = await putUpdateI18nAPI(form, row.id); | ||||
|           if (res.success) { | ||||
|             done(); | ||||
|             await onSearch(); | ||||
|           } | ||||
|           message(res.msg, { type: res.success ? "success" : "error" }); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   /**语言类型 */ | ||||
|   const localeList = ref<LanguageInfo[]>([]); | ||||
| 
 | ||||
|   /** | ||||
|    * 获取语言类型 | ||||
|    */ | ||||
|   const getLocaleList = async () => { | ||||
|     const res = await getLocaleListAPI({ | ||||
|       page: 1, | ||||
|       pageSize: 9999 | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       localeList.value = res.data.result; | ||||
|     } | ||||
|     message(res.msg, { | ||||
|       type: res.success ? "success" : "error" | ||||
|     }); | ||||
|   }; | ||||
|   /** | ||||
|    * 页面加载执行 | ||||
|    */ | ||||
|   onMounted(async () => { | ||||
|     await onSearch(); | ||||
|     await getLocaleList(); | ||||
|   }); | ||||
| 
 | ||||
|   return { | ||||
|     form, | ||||
|     formRef, | ||||
|     dataList, | ||||
|     loading, | ||||
|     pagination, | ||||
|     columns, | ||||
|     selectedNum, | ||||
|     localeList, | ||||
|     onSearch, | ||||
|     openDialog, | ||||
|     resetForm, | ||||
|     handleDelete, | ||||
|     handleSizeChange, | ||||
|     handleCurrentChange, | ||||
|     handleSelectionChange, | ||||
|     onSelectionCancel, | ||||
|     onbatchDel, | ||||
|     getLocaleList | ||||
|   }; | ||||
| }; | ||||
							
								
								
									
										50
									
								
								src/views/system/locale/components/form.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/views/system/locale/components/form.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | ||||
| <template> | ||||
|   <el-form ref="ruleFormRef" :model="newFormInline" label-width="82px"> | ||||
|     <el-row :gutter="30"> | ||||
|       <re-col :value="24" :xm="24" :sm="24"> | ||||
|         <el-form-item label="语言编码" prop="code"> | ||||
|           <el-input | ||||
|             v-model="newFormInline.code" | ||||
|             placeholder="请输入语言编码~" | ||||
|             clearable | ||||
|             class="w-full" | ||||
|           /> | ||||
|         </el-form-item> | ||||
|       </re-col> | ||||
|       <re-col :value="24" :xm="24" :sm="24"> | ||||
|         <el-form-item label="语言名称" prop="name"> | ||||
|           <el-input | ||||
|             v-model="newFormInline.name" | ||||
|             placeholder="请输入语言名称~" | ||||
|             clearable | ||||
|             class="w-full" | ||||
|           /> | ||||
|         </el-form-item> | ||||
|       </re-col> | ||||
|     </el-row> | ||||
|   </el-form> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| import { ref } from "vue"; | ||||
| import ReCol from "@/components/ReCol"; | ||||
| const ruleFormRef = ref(); | ||||
| interface PropsInfo { | ||||
|   title: string; | ||||
|   code: string; | ||||
|   name: string; | ||||
| } | ||||
| 
 | ||||
| type ProsData = { | ||||
|   formInline: PropsInfo; | ||||
| }; | ||||
| const props = withDefaults(defineProps<ProsData>(), { | ||||
|   formInline: () => ({ | ||||
|     title: "新增", | ||||
|     code: "", | ||||
|     name: "" | ||||
|   }) | ||||
| }); | ||||
| const newFormInline = ref<PropsInfo>(props.formInline); | ||||
| defineExpose({ newFormInline }); | ||||
| </script> | ||||
							
								
								
									
										219
									
								
								src/views/system/locale/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								src/views/system/locale/index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,219 @@ | ||||
| <template> | ||||
|   <div class="main"> | ||||
|     <el-form | ||||
|       ref="formRef" | ||||
|       :inline="true" | ||||
|       :model="form" | ||||
|       class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]" | ||||
|     > | ||||
|       <el-form-item label="语言编码" prop="code"> | ||||
|         <el-input | ||||
|           v-model="form.code" | ||||
|           placeholder="请输入语言编码~" | ||||
|           clearable | ||||
|           class="!w-[180px]" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="语言名称" prop="name"> | ||||
|         <el-input | ||||
|           v-model="form.name" | ||||
|           placeholder="请输入语言名称~" | ||||
|           clearable | ||||
|           class="!w-[180px]" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
|         <el-button | ||||
|           type="primary" | ||||
|           :icon="useRenderIcon('ri:search-line')" | ||||
|           :loading="loading" | ||||
|           @click="onSearch" | ||||
|         > | ||||
|           {{ t("buttons:Search") }} | ||||
|         </el-button> | ||||
|         <el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> | ||||
|           {{ t("buttons:Reset") }} | ||||
|         </el-button> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|     <PureTableBar title="语言类型管理" :columns="columns" @refresh="onSearch"> | ||||
|       <template #buttons> | ||||
|         <el-button | ||||
|           v-if="hasAuth('locale:btn:add')" | ||||
|           type="primary" | ||||
|           :icon="useRenderIcon(AddFill)" | ||||
|           @click="openDialog('新增')" | ||||
|         > | ||||
|           {{ t("buttons:Add") }} | ||||
|         </el-button> | ||||
|       </template> | ||||
|       <template v-slot="{ size, dynamicColumns }"> | ||||
|         <div | ||||
|           v-if="selectedNum > 0" | ||||
|           v-motion-fade | ||||
|           class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center" | ||||
|         > | ||||
|           <div class="flex-auto"> | ||||
|             <span | ||||
|               style="font-size: var(--el-font-size-base)" | ||||
|               class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]" | ||||
|             > | ||||
|               已选 {{ selectedNum }} 项 | ||||
|             </span> | ||||
|             <el-button type="primary" text @click="onSelectionCancel"> | ||||
|               {{ t("buttons:Deselect") }} | ||||
|             </el-button> | ||||
|           </div> | ||||
|           <el-popconfirm | ||||
|             v-if="hasAuth('locale:btn:delete')" | ||||
|             title="是否确认删除?" | ||||
|             @confirm="onbatchDel" | ||||
|           > | ||||
|             <template #reference> | ||||
|               <el-button | ||||
|                 type="danger" | ||||
|                 :disabled="!selectedNum || !hasAuth('locale:btn:delete')" | ||||
|                 text | ||||
|                 class="mr-1" | ||||
|               > | ||||
|                 {{ t("buttons:DeleteInBatches") }}</el-button | ||||
|               > | ||||
|             </template> | ||||
|           </el-popconfirm> | ||||
|         </div> | ||||
|         <pure-table | ||||
|           ref="tableRef" | ||||
|           row-key="id" | ||||
|           adaptive | ||||
|           border | ||||
|           stripe | ||||
|           :adaptiveConfig="{ offsetBottom: 45 }" | ||||
|           align-whole="center" | ||||
|           table-layout="auto" | ||||
|           :loading="loading" | ||||
|           :size="size" | ||||
|           :data="dataList" | ||||
|           :columns="dynamicColumns" | ||||
|           :pagination="pagination" | ||||
|           :paginationSmall="size === 'small' ? true : false" | ||||
|           :header-cell-style="{ | ||||
|             background: 'var(--el-fill-color-light)', | ||||
|             color: 'var(--el-text-color-primary)' | ||||
|           }" | ||||
|           @selection-change="handleSelectionChange" | ||||
|           @page-size-change="handleSizeChange" | ||||
|           @page-current-change="handleCurrentChange" | ||||
|         > | ||||
|           <template #operation="{ row }"> | ||||
|             <el-button | ||||
|               class="reset-margin" | ||||
|               link | ||||
|               type="primary" | ||||
|               :size="size" | ||||
|               :icon="useRenderIcon(EditPen)" | ||||
|               :disabled="!hasAuth('locale:btn:update')" | ||||
|               @click="openDialog('修改', row)" | ||||
|             > | ||||
|               {{ t("buttons:Update") }} | ||||
|             </el-button> | ||||
|             <el-popconfirm | ||||
|               :title="`是否确认导出语言名称为 ${row.name} 的这条数据为yaml文件`" | ||||
|               @confirm="export_to_yaml(row)" | ||||
|             > | ||||
|               <template #reference> | ||||
|                 <el-button | ||||
|                   class="reset-margin" | ||||
|                   link | ||||
|                   type="primary" | ||||
|                   :size="size" | ||||
|                   :disabled="!hasAuth('i18n:btn:infoList')" | ||||
|                   :icon="useRenderIcon(Download)" | ||||
|                 > | ||||
|                   {{ t("buttons:Export") }} | ||||
|                 </el-button> | ||||
|               </template> | ||||
|             </el-popconfirm> | ||||
|             <el-popconfirm | ||||
|               :title="`是否确认删除语言名称为 ${row.name} 的这条数据`" | ||||
|               @confirm="handleDelete(row)" | ||||
|             > | ||||
|               <template #reference> | ||||
|                 <el-button | ||||
|                   class="reset-margin" | ||||
|                   link | ||||
|                   type="danger" | ||||
|                   :size="size" | ||||
|                   :disabled="!hasAuth('locale:btn:delete')" | ||||
|                   :icon="useRenderIcon(Delete)" | ||||
|                 > | ||||
|                   {{ t("buttons:Delete") }} | ||||
|                 </el-button> | ||||
|               </template> | ||||
|             </el-popconfirm> | ||||
|           </template> | ||||
|         </pure-table> | ||||
|       </template> | ||||
|     </PureTableBar> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| defineOptions({ | ||||
|   name: "LocaleIndex" | ||||
| }); | ||||
| import { ref } from "vue"; | ||||
| import { useLocale } from "./utils/hook"; | ||||
| import { PureTableBar } from "@/components/RePureTableBar"; | ||||
| import { useRenderIcon } from "@/components/ReIcon/src/hooks"; | ||||
| import { useI18n } from "vue-i18n"; | ||||
| import Delete from "@iconify-icons/ep/delete"; | ||||
| import EditPen from "@iconify-icons/ep/edit-pen"; | ||||
| import Refresh from "@iconify-icons/ep/refresh"; | ||||
| import AddFill from "@iconify-icons/ri/add-circle-line"; | ||||
| import Download from "@iconify-icons/ri/file-download-line"; | ||||
| const { t } = useI18n(); | ||||
| import { hasAuth } from "@/utils/auth"; | ||||
| /** | ||||
|  * 表格Ref | ||||
|  */ | ||||
| const tableRef = ref(); | ||||
| const { | ||||
|   form, | ||||
|   formRef, | ||||
|   dataList, | ||||
|   loading, | ||||
|   pagination, | ||||
|   columns, | ||||
|   selectedNum, | ||||
|   onSearch, | ||||
|   openDialog, | ||||
|   resetForm, | ||||
|   export_to_yaml, | ||||
|   handleDelete, | ||||
|   handleSizeChange, | ||||
|   handleCurrentChange, | ||||
|   handleSelectionChange, | ||||
|   onSelectionCancel, | ||||
|   onbatchDel | ||||
| } = useLocale(tableRef); | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"> | ||||
| :deep(.el-dropdown-menu__item i) { | ||||
|   margin: 0; | ||||
| } | ||||
| 
 | ||||
| :deep(.el-button:focus-visible) { | ||||
|   outline: none; | ||||
| } | ||||
| 
 | ||||
| .main-content { | ||||
|   margin: 24px 24px 0 !important; | ||||
| } | ||||
| 
 | ||||
| .search-form { | ||||
|   :deep(.el-form-item) { | ||||
|     margin-bottom: 12px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										303
									
								
								src/views/system/locale/utils/hook.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								src/views/system/locale/utils/hook.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,303 @@ | ||||
| import dayjs from "dayjs"; | ||||
| import editForm from "../components/form.vue"; | ||||
| import { message } from "@/utils/message"; | ||||
| import { type Ref, ref, reactive, onMounted, h } from "vue"; | ||||
| import type { LanguageInfo, TranslationInfo } from "types/system"; | ||||
| import type { PaginationProps } from "@pureadmin/table"; | ||||
| import { addDialog } from "@/components/ReDialog"; | ||||
| import { | ||||
|   getLocaleListAPI, | ||||
|   deleteLocaleAPI, | ||||
|   deleteLocaleListAPI, | ||||
|   postAddLocaleAPI, | ||||
|   putUpdateLocaleAPI, | ||||
|   getI18nHandleListAPI | ||||
| } from "@/api/i18n"; | ||||
| 
 | ||||
| import jsyaml from "js-yaml"; | ||||
| import { getKeyList } from "@pureadmin/utils"; | ||||
| 
 | ||||
| export const useLocale = (tableRef: Ref) => { | ||||
|   /** | ||||
|    * 查询表单 | ||||
|    */ | ||||
|   const form = reactive({ | ||||
|     name: "", | ||||
|     code: "" | ||||
|   }); | ||||
|   /** | ||||
|    * 表单Ref | ||||
|    */ | ||||
|   const formRef = ref(null); | ||||
|   /** | ||||
|    * 数据列表 | ||||
|    */ | ||||
|   const dataList = ref<LanguageInfo[]>([]); | ||||
|   /** | ||||
|    * 加载状态 | ||||
|    */ | ||||
|   const loading = ref(true); | ||||
|   /** | ||||
|    * 已选数量 | ||||
|    */ | ||||
|   const selectedNum = ref<number>(0); | ||||
|   /** | ||||
|    * 分页参数 | ||||
|    */ | ||||
|   const pagination = reactive<PaginationProps>({ | ||||
|     total: 0, | ||||
|     pageSize: 10, | ||||
|     currentPage: 1, | ||||
|     background: true, | ||||
|     pageSizes: [10, 20, 30, 40, 50] | ||||
|   }); | ||||
|   /** | ||||
|    * 表格列设置 | ||||
|    */ | ||||
|   const columns: TableColumnList = [ | ||||
|     { | ||||
|       label: "勾选列", // 如果需要表格多选,此处label必须设置
 | ||||
|       type: "selection", | ||||
|       fixed: "left", | ||||
|       reserveSelection: true // 数据刷新后保留选项
 | ||||
|     }, | ||||
|     { | ||||
|       label: "语言编码", | ||||
|       prop: "code" | ||||
|     }, | ||||
|     { | ||||
|       label: "语言名称", | ||||
|       prop: "name" | ||||
|     }, | ||||
|     { | ||||
|       label: "创建时间", | ||||
|       prop: "create_time", | ||||
|       formatter: ({ create_time }) => | ||||
|         dayjs(create_time).format("YYYY-MM-DD HH:mm:ss") | ||||
|     }, | ||||
|     { | ||||
|       label: "操作", | ||||
|       fixed: "right", | ||||
|       width: 250, | ||||
|       slot: "operation" | ||||
|     } | ||||
|   ]; | ||||
| 
 | ||||
|   /** | ||||
|    * 初次查询 | ||||
|    */ | ||||
|   const onSearch = async () => { | ||||
|     loading.value = true; | ||||
|     const res = await getLocaleListAPI({ | ||||
|       page: pagination.currentPage, | ||||
|       pageSize: pagination.pageSize, | ||||
|       name: form.name, | ||||
|       code: form.code | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|     } | ||||
|     message(res.msg, { | ||||
|       type: res.success ? "success" : "error" | ||||
|     }); | ||||
|     loading.value = false; | ||||
|   }; | ||||
|   /** | ||||
|    * 重置表单 | ||||
|    * @param formEl 表单ref | ||||
|    * @returns | ||||
|    */ | ||||
|   const resetForm = (formEl: any) => { | ||||
|     if (!formEl) return; | ||||
|     formEl.resetFields(); | ||||
|     onSearch(); | ||||
|   }; | ||||
|   /** | ||||
|    * 处理删除 | ||||
|    * @param row | ||||
|    */ | ||||
|   const handleDelete = async (row: TranslationInfo) => { | ||||
|     const res = await deleteLocaleAPI(row.id); | ||||
|     if (res.success) { | ||||
|       onSearch(); | ||||
|     } | ||||
|     message(res.msg, { | ||||
|       type: res.success ? "success" : "error" | ||||
|     }); | ||||
|   }; | ||||
|   /** | ||||
|    * 处理每页数量变化 | ||||
|    */ | ||||
|   const handleSizeChange = async (val: number) => { | ||||
|     const res = await getLocaleListAPI({ | ||||
|       page: pagination.currentPage, | ||||
|       pageSize: val, | ||||
|       name: form.name, | ||||
|       code: form.code | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|     } | ||||
|     message(res.msg, { | ||||
|       type: res.success ? "success" : "error" | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 处理页码变化 | ||||
|    * @param val | ||||
|    */ | ||||
|   const handleCurrentChange = async (val: number) => { | ||||
|     const res = await getLocaleListAPI({ | ||||
|       page: val, | ||||
|       pageSize: pagination.pageSize, | ||||
|       name: form.name, | ||||
|       code: form.code | ||||
|     }); | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|     } | ||||
|     message(res.msg, { | ||||
|       type: res.success ? "success" : "error" | ||||
|     }); | ||||
|   }; | ||||
|   /** 当CheckBox选择项发生变化时会触发该事件 */ | ||||
|   const handleSelectionChange = async (val: any) => { | ||||
|     selectedNum.value = val.length; | ||||
|     // 重置表格高度
 | ||||
|     tableRef.value.setAdaptive(); | ||||
|   }; | ||||
| 
 | ||||
|   /** 取消选择 */ | ||||
|   const onSelectionCancel = async () => { | ||||
|     selectedNum.value = 0; | ||||
|     // 用于多选表格,清空用户的选择
 | ||||
|     tableRef.value.getTableRef().clearSelection(); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 批量删除 | ||||
|    */ | ||||
|   const onbatchDel = async () => { | ||||
|     // 返回当前选中的行
 | ||||
|     const curSelected = tableRef.value.getTableRef().getSelectionRows(); | ||||
|     const res = await deleteLocaleListAPI(getKeyList(curSelected, "id")); | ||||
|     if (res.success) { | ||||
|       message(res.msg, { | ||||
|         type: "success" | ||||
|       }); | ||||
|       tableRef.value.getTableRef().clearSelection(); | ||||
|       onSearch(); | ||||
|     } else { | ||||
|       message(res.msg, { type: "error", duration: 5000 }); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const openDialog = async (title = "新增", row?: LanguageInfo) => { | ||||
|     addDialog({ | ||||
|       title: `${title}国际化项`, | ||||
|       props: { | ||||
|         formInline: { | ||||
|           title: title, | ||||
|           name: row?.name ?? "", | ||||
|           code: row?.code ?? "" | ||||
|         } | ||||
|       }, | ||||
|       width: "45%", | ||||
|       draggable: true, | ||||
|       fullscreenIcon: true, | ||||
|       closeOnClickModal: false, | ||||
|       contentRenderer: () => | ||||
|         h(editForm, { | ||||
|           formInline: { | ||||
|             title: title, | ||||
|             name: row?.name ?? "", | ||||
|             code: row?.code ?? "" | ||||
|           }, | ||||
|           ref: formRef | ||||
|         }), | ||||
|       beforeSure: async (done, {}) => { | ||||
|         const FormData = formRef.value.newFormInline; | ||||
|         let form = { | ||||
|           name: FormData.name ?? "", | ||||
|           code: FormData.code ?? "" | ||||
|         }; | ||||
|         if (title === "新增") { | ||||
|           const res = await postAddLocaleAPI(form); | ||||
|           if (res.success) { | ||||
|             done(); | ||||
|             await onSearch(); | ||||
|           } | ||||
|           message(res.msg, { type: res.success ? "success" : "error" }); | ||||
|         } else { | ||||
|           const res = await putUpdateLocaleAPI(form, row.id); | ||||
|           if (res.success) { | ||||
|             done(); | ||||
|             await onSearch(); | ||||
|           } | ||||
|           message(res.msg, { type: res.success ? "success" : "error" }); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 导出 YAML 文件 | ||||
|    */ | ||||
|   const export_to_yaml = async (row: LanguageInfo) => { | ||||
|     const res = await getI18nHandleListAPI(row.id); // 调用 API 获取数据
 | ||||
|     if (res.success) { | ||||
|       // 将 JSON 转换为 YAML
 | ||||
|       const yamlString = jsyaml.dump(res.data.data); | ||||
| 
 | ||||
|       // 创建 Blob 对象
 | ||||
|       const blob = new Blob([yamlString], { type: "text/yaml" }); | ||||
| 
 | ||||
|       // 生成下载链接
 | ||||
|       const url = URL.createObjectURL(blob); | ||||
| 
 | ||||
|       // 创建 <a> 元素并触发下载
 | ||||
|       const link = document.createElement("a"); | ||||
|       link.href = url; | ||||
|       link.download = `${row.code}.yaml`; // 设置下载文件名
 | ||||
|       document.body.appendChild(link); // 将 <a> 元素添加到 DOM 中
 | ||||
|       link.click(); // 模拟点击下载
 | ||||
| 
 | ||||
|       // 清理 URL 对象
 | ||||
|       URL.revokeObjectURL(url); | ||||
|       document.body.removeChild(link); // 移除 <a> 元素
 | ||||
|     } | ||||
|   }; | ||||
|   /** | ||||
|    * 页面加载执行 | ||||
|    */ | ||||
|   onMounted(async () => { | ||||
|     await onSearch(); | ||||
|   }); | ||||
| 
 | ||||
|   return { | ||||
|     form, | ||||
|     formRef, | ||||
|     dataList, | ||||
|     loading, | ||||
|     pagination, | ||||
|     columns, | ||||
|     selectedNum, | ||||
|     onSearch, | ||||
|     openDialog, | ||||
|     resetForm, | ||||
|     export_to_yaml, | ||||
|     handleDelete, | ||||
|     handleSizeChange, | ||||
|     handleCurrentChange, | ||||
|     handleSelectionChange, | ||||
|     onSelectionCancel, | ||||
|     onbatchDel | ||||
|   }; | ||||
| }; | ||||
| @ -224,11 +224,14 @@ defineExpose({ getRef }); | ||||
|       </re-col> | ||||
| 
 | ||||
|       <re-col :value="12" :xs="24" :sm="24"> | ||||
|         <el-form-item label="菜单名称" prop="title"> | ||||
|         <el-form-item | ||||
|           :label="newFormInline.menu_type === 3 ? '权限名称' : '菜单名称'" | ||||
|           prop="title" | ||||
|         > | ||||
|           <el-input | ||||
|             v-model="newFormInline.title" | ||||
|             clearable | ||||
|             placeholder="请输入菜单名称" | ||||
|             :placeholder="`请输入${newFormInline.menu_type === 3 ? '权限名称' : '菜单名称'}~`" | ||||
|           /> | ||||
|         </el-form-item> | ||||
|       </re-col> | ||||
| @ -287,7 +290,9 @@ defineExpose({ getRef }); | ||||
|       </re-col> | ||||
| 
 | ||||
|       <re-col :value="12" :xs="24" :sm="24"> | ||||
|         <el-form-item label="菜单排序"> | ||||
|         <el-form-item | ||||
|           :label="newFormInline.menu_type === 3 ? '权限排序' : '菜单排序'" | ||||
|         > | ||||
|           <el-input-number | ||||
|             v-model="newFormInline.rank" | ||||
|             class="!w-full" | ||||
|  | ||||
| @ -10,6 +10,7 @@ import EditPen from "@iconify-icons/ep/edit-pen"; | ||||
| import Refresh from "@iconify-icons/ep/refresh"; | ||||
| import AddFill from "@iconify-icons/ri/add-circle-line"; | ||||
| import { onBeforeRouteUpdate } from "vue-router"; | ||||
| import { hasAuth } from "@/utils/auth"; | ||||
| const { t } = useI18n(); | ||||
| defineOptions({ | ||||
|   name: "SystemPermission" | ||||
| @ -78,6 +79,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|     > | ||||
|       <template #buttons> | ||||
|         <el-button | ||||
|           v-if="hasAuth('permission:btn:add')" | ||||
|           type="primary" | ||||
|           :icon="useRenderIcon(AddFill)" | ||||
|           @click="openDialog()" | ||||
| @ -111,6 +113,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|               link | ||||
|               type="primary" | ||||
|               :size="size" | ||||
|               :disabled="!hasAuth('permission:btn:update')" | ||||
|               :icon="useRenderIcon(EditPen)" | ||||
|               @click="openDialog('修改', row)" | ||||
|             > | ||||
| @ -122,6 +125,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|               link | ||||
|               type="primary" | ||||
|               :size="size" | ||||
|               :disabled="!hasAuth('permission:btn:add')" | ||||
|               :icon="useRenderIcon(AddFill)" | ||||
|               @click="openDialog('新增', { parent_id: row.id } as any)" | ||||
|             > | ||||
| @ -135,7 +139,8 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|                 <el-button | ||||
|                   class="reset-margin" | ||||
|                   link | ||||
|                   type="primary" | ||||
|                   type="danger" | ||||
|                   :disabled="!hasAuth('permission:btn:delete')" | ||||
|                   :size="size" | ||||
|                   :icon="useRenderIcon(Delete)" | ||||
|                 > | ||||
|  | ||||
| @ -267,7 +267,7 @@ export const usePermission = () => { | ||||
|                 } | ||||
|               } | ||||
|               const res = await postAddPermissionAPI(addForm); | ||||
|               if (res.code === 200) { | ||||
|               if (res.success) { | ||||
|                 // 实际开发先调用新增接口,再进行下面操作
 | ||||
|                 chores(); | ||||
|               } else { | ||||
| @ -305,7 +305,7 @@ export const usePermission = () => { | ||||
|                 } | ||||
|               } | ||||
|               const res = await putUpdatePermissionAPI(curData.id, updateForm); | ||||
|               if (res.code === 200) { | ||||
|               if (res.success) { | ||||
|                 chores(); | ||||
|               } else { | ||||
|                 message(`更新失败!`, { type: "error" }); | ||||
| @ -318,7 +318,7 @@ export const usePermission = () => { | ||||
|   }; | ||||
|   const handleDelete = async (row: PermissionInfo) => { | ||||
|     const res = await deletePermissionAPI(row.id); | ||||
|     if (res.code === 200) { | ||||
|     if (res.success) { | ||||
|       message(`您删除了权限名称为${transformI18n(row.title)}的这条数据`, { | ||||
|         type: "success" | ||||
|       }); | ||||
|  | ||||
| @ -4,6 +4,7 @@ import type { FormRules } from "element-plus"; | ||||
| import { getDepartmentListAPI } from "@/api/system"; | ||||
| import type { DepartmentInfo } from "types/system"; | ||||
| import { usePublicHooks } from "../../hooks"; | ||||
| import { handleTree } from "@pureadmin/utils"; | ||||
| 
 | ||||
| defineOptions({ | ||||
|   name: "SystemRoleForm" | ||||
| @ -52,8 +53,10 @@ function getRef() { | ||||
| /**获取部门列表 */ | ||||
| const getDepartments = async () => { | ||||
|   const res = await getDepartmentListAPI({ page: 1, pageSize: 9999 }); | ||||
|   if (res.code === 200) { | ||||
|     departments.value = formatHigherOptions(res.data.result); | ||||
|   if (res.success) { | ||||
|     departments.value = formatHigherOptions( | ||||
|       handleTree(res.data.result, "id", "parent_id") | ||||
|     ); | ||||
|   } else { | ||||
|     departments.value = []; | ||||
|   } | ||||
|  | ||||
| @ -19,6 +19,7 @@ import Close from "@iconify-icons/ep/close"; | ||||
| import Check from "@iconify-icons/ep/check"; | ||||
| import { onBeforeRouteUpdate } from "vue-router"; | ||||
| const { t } = useI18n(); | ||||
| import { hasAuth } from "@/utils/auth"; | ||||
| defineOptions({ | ||||
|   name: "SystemRole" | ||||
| }); | ||||
| @ -51,8 +52,8 @@ const { | ||||
|   curRow, | ||||
|   loading, | ||||
|   columns, | ||||
|   rowStyle, | ||||
|   dataList, | ||||
|   selectedNum, | ||||
|   treeData, | ||||
|   treeProps, | ||||
|   isLinkage, | ||||
| @ -61,8 +62,11 @@ const { | ||||
|   isSelectAll, | ||||
|   treeSearchValue, | ||||
|   departments, | ||||
|   rowStyle, | ||||
|   onSearch, | ||||
|   resetForm, | ||||
|   onSelectionCancel, | ||||
|   onbatchDel, | ||||
|   openDialog, | ||||
|   handleMenu, | ||||
|   handleSave, | ||||
| @ -71,8 +75,9 @@ const { | ||||
|   transformI18n, | ||||
|   onQueryChanged, | ||||
|   handleSizeChange, | ||||
|   handleCurrentChange | ||||
| } = useRole(treeRef); | ||||
|   handleCurrentChange, | ||||
|   handleSelectionChange | ||||
| } = useRole(treeRef, tableRef); | ||||
| onMounted(() => { | ||||
|   useResizeObserver(contentRef, async () => { | ||||
|     await nextTick(); | ||||
| @ -180,6 +185,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|       > | ||||
|         <template #buttons> | ||||
|           <el-button | ||||
|             v-if="hasAuth('role:btn:add')" | ||||
|             type="primary" | ||||
|             :icon="useRenderIcon(AddFill)" | ||||
|             @click="openDialog()" | ||||
| @ -188,8 +194,42 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|           </el-button> | ||||
|         </template> | ||||
|         <template v-slot="{ size, dynamicColumns }"> | ||||
|           <div | ||||
|             v-if="selectedNum > 0" | ||||
|             v-motion-fade | ||||
|             class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center" | ||||
|           > | ||||
|             <div class="flex-auto"> | ||||
|               <span | ||||
|                 style="font-size: var(--el-font-size-base)" | ||||
|                 class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]" | ||||
|               > | ||||
|                 已选 {{ selectedNum }} 项 | ||||
|               </span> | ||||
|               <el-button type="primary" text @click="onSelectionCancel"> | ||||
|                 {{ t("buttons:Deselect") }} | ||||
|               </el-button> | ||||
|             </div> | ||||
|             <el-popconfirm | ||||
|               v-if="hasAuth('role:btn:delete')" | ||||
|               title="是否确认删除?" | ||||
|               @confirm="onbatchDel" | ||||
|             > | ||||
|               <template #reference> | ||||
|                 <el-button | ||||
|                   type="danger" | ||||
|                   text | ||||
|                   class="mr-1" | ||||
|                   :disabled="selectedNum < 0 || hasAuth('role:btn:delete')" | ||||
|                 > | ||||
|                   {{ t("buttons:DeleteInBatches") }} | ||||
|                 </el-button> | ||||
|               </template> | ||||
|             </el-popconfirm> | ||||
|           </div> | ||||
|           <pure-table | ||||
|             ref="tableRef" | ||||
|             row-key="id" | ||||
|             align-whole="center" | ||||
|             showOverflowTooltip | ||||
|             table-layout="auto" | ||||
| @ -208,6 +248,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|               background: 'var(--el-fill-color-light)', | ||||
|               color: 'var(--el-text-color-primary)' | ||||
|             }" | ||||
|             @selection-change="handleSelectionChange" | ||||
|             @page-size-change="handleSizeChange" | ||||
|             @page-current-change="handleCurrentChange" | ||||
|           > | ||||
| @ -217,6 +258,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|                 link | ||||
|                 type="primary" | ||||
|                 :size="size" | ||||
|                 :disabled="!hasAuth('role:btn:update')" | ||||
|                 :icon="useRenderIcon(EditPen)" | ||||
|                 @click="openDialog('修改', row)" | ||||
|               > | ||||
| @ -232,6 +274,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|                     link | ||||
|                     type="danger" | ||||
|                     :size="size" | ||||
|                     :disabled="!hasAuth('role:btn:delete')" | ||||
|                     :icon="useRenderIcon(Delete)" | ||||
|                   > | ||||
|                     {{ t("buttons:Delete") }} | ||||
| @ -243,6 +286,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|                 link | ||||
|                 type="primary" | ||||
|                 :size="size" | ||||
|                 :disabled="!hasAuth('role:btn:permissionInfo')" | ||||
|                 :icon="useRenderIcon(Menu)" | ||||
|                 @click="handleMenu(row)" | ||||
|               > | ||||
|  | ||||
| @ -4,6 +4,7 @@ import { message } from "@/utils/message"; | ||||
| 
 | ||||
| import { | ||||
|   deleteRoleAPI, | ||||
|   deleteRoleListAPI, | ||||
|   getPermissionListAPI, | ||||
|   getRoleListAPI, | ||||
|   postAddRoleAPI, | ||||
| @ -26,7 +27,7 @@ import type { | ||||
|   RolePermissionInfo | ||||
| } from "types/system"; | ||||
| 
 | ||||
| export const useRole = (treeRef: Ref) => { | ||||
| export const useRole = (treeRef: Ref, tableRef: Ref) => { | ||||
|   /**查询表单 */ | ||||
|   const form = reactive({ | ||||
|     name: "", | ||||
| @ -47,6 +48,10 @@ export const useRole = (treeRef: Ref) => { | ||||
|    * 加载状态 | ||||
|    */ | ||||
|   const loading = ref(true); | ||||
|   /** | ||||
|    * 已选数量 | ||||
|    */ | ||||
|   const selectedNum = ref<number>(0); | ||||
|   /** | ||||
|    * 标签样式 | ||||
|    */ | ||||
| @ -87,6 +92,12 @@ export const useRole = (treeRef: Ref) => { | ||||
|   /**是否全选 */ | ||||
|   const isSelectAll = ref<boolean>(false); | ||||
|   const columns: TableColumnList = [ | ||||
|     { | ||||
|       label: "勾选列", // 如果需要表格多选,此处label必须设置
 | ||||
|       type: "selection", | ||||
|       fixed: "left", | ||||
|       reserveSelection: true // 数据刷新后保留选项
 | ||||
|     }, | ||||
|     { | ||||
|       label: "角色名称", | ||||
|       prop: "name", | ||||
| @ -150,7 +161,10 @@ export const useRole = (treeRef: Ref) => { | ||||
|     dataList.value = data.data.result; | ||||
|     pagination.total = data.data.total; | ||||
|     pagination.currentPage = data.data.page; | ||||
| 
 | ||||
|     pagination.pageSize = data.data.pageSize; | ||||
|     message(data.msg, { | ||||
|       type: data.success ? "success" : "error" | ||||
|     }); | ||||
|     setTimeout(() => { | ||||
|       loading.value = false; | ||||
|     }, 500); | ||||
| @ -166,30 +180,69 @@ export const useRole = (treeRef: Ref) => { | ||||
|       page: pagination.currentPage, | ||||
|       pageSize: val | ||||
|     }); | ||||
|     if (res.code === 200) { | ||||
|     if (res.success) { | ||||
|       const data = res.data; | ||||
|       dataList.value = data.result; | ||||
|       pagination.total = data.total; | ||||
|       pagination.currentPage = data.page; | ||||
|       pagination.pageSize = res.data.pageSize; | ||||
|     } | ||||
|   }; | ||||
|   /**处理每页数量变化 */ | ||||
|   /** | ||||
|    * 处理页码变化 | ||||
|    * @param val | ||||
|    */ | ||||
|   const handleCurrentChange = async (val: number) => { | ||||
|     loading.value = true; | ||||
|     const res = await getRoleListAPI({ | ||||
|       page: val, | ||||
|       pageSize: pagination.pageSize | ||||
|       pageSize: pagination.pageSize, | ||||
|       ...toRaw(form) | ||||
|     }); | ||||
|     if (res.code === 200) { | ||||
|       const data = res.data; | ||||
|       dataList.value = data.result; | ||||
|       pagination.total = data.total; | ||||
|       pagination.currentPage = data.page; | ||||
|     if (res.success) { | ||||
|       dataList.value = res.data.result; | ||||
|       pagination.total = res.data.total; | ||||
|       pagination.currentPage = res.data.page; | ||||
|       pagination.pageSize = res.data.pageSize; | ||||
|     } | ||||
|     message(res.msg, { | ||||
|       type: res.success ? "success" : "error" | ||||
|     }); | ||||
|     loading.value = false; | ||||
|   }; | ||||
|   /** 当CheckBox选择项发生变化时会触发该事件 */ | ||||
|   const handleSelectionChange = async (val: any) => { | ||||
|     selectedNum.value = val.length; | ||||
|     // 重置表格高度
 | ||||
|     tableRef.value.setAdaptive(); | ||||
|   }; | ||||
| 
 | ||||
|   /** 取消选择 */ | ||||
|   const onSelectionCancel = async () => { | ||||
|     selectedNum.value = 0; | ||||
|     // 用于多选表格,清空用户的选择
 | ||||
|     tableRef.value.getTableRef().clearSelection(); | ||||
|   }; | ||||
|   /** | ||||
|    * 批量删除 | ||||
|    */ | ||||
|   const onbatchDel = async () => { | ||||
|     // 返回当前选中的行
 | ||||
|     const curSelected = tableRef.value.getTableRef().getSelectionRows(); | ||||
|     const res = await deleteRoleListAPI(getKeyList(curSelected, "id")); | ||||
|     if (res.success) { | ||||
|       message(res.msg, { | ||||
|         type: "success" | ||||
|       }); | ||||
|       tableRef.value.getTableRef().clearSelection(); | ||||
|       onSearch(); | ||||
|     } else { | ||||
|       message(res.msg, { type: "error", duration: 5000 }); | ||||
|     } | ||||
|   }; | ||||
|   const handleDelete = async (row: RoleInfo) => { | ||||
|     const res = await deleteRoleAPI(row.id); | ||||
|     if (res.code === 200) { | ||||
|     if (res.success) { | ||||
|       message(`您删除了角色名称为${row.name}的这条数据`, { type: "success" }); | ||||
|       onSearch(); | ||||
|     } else { | ||||
| @ -237,12 +290,11 @@ export const useRole = (treeRef: Ref) => { | ||||
|         } | ||||
|         FormRef.validate(async (valid: any) => { | ||||
|           if (valid) { | ||||
|             console.log("curData", curData); | ||||
|             // 表单规则校验通过
 | ||||
|             if (title === "新增") { | ||||
|               // 实际开发先调用新增接口,再进行下面操作
 | ||||
|               const res = await postAddRoleAPI(curData); | ||||
|               if (res.code === 200) { | ||||
|               if (res.success) { | ||||
|                 chores(); | ||||
|               } else { | ||||
|                 message(`添加失败!`, { | ||||
| @ -253,7 +305,7 @@ export const useRole = (treeRef: Ref) => { | ||||
|             } else { | ||||
|               // 实际开发先调用修改接口,再进行下面操作
 | ||||
|               const res = await putUpdateRoleAPI(curData, row.id); | ||||
|               if (res.code === 200) { | ||||
|               if (res.success) { | ||||
|                 chores(); | ||||
|               } else { | ||||
|                 message(`修改失败!`, { | ||||
| @ -310,7 +362,7 @@ export const useRole = (treeRef: Ref) => { | ||||
|     const res = await putUpdateRolePermissionsAPI(id, { | ||||
|       permission_ids: permissions | ||||
|     }); | ||||
|     if (res.code === 200) { | ||||
|     if (res.success) { | ||||
|       message(`角色名称为${name}的权限修改成功~`, { | ||||
|         type: "success" | ||||
|       }); | ||||
| @ -334,14 +386,16 @@ export const useRole = (treeRef: Ref) => { | ||||
|   /**获取部门列表 */ | ||||
|   const getDepartments = async () => { | ||||
|     const res = await getDepartmentListAPI({ page: 1, pageSize: 9999 }); | ||||
|     if (res.code === 200) { | ||||
|       departments.value = formatHigherOptions(res.data.result); | ||||
|     if (res.success) { | ||||
|       departments.value = formatHigherOptions( | ||||
|         handleTree(res.data.result, "id", "parent_id") | ||||
|       ); | ||||
|     } else { | ||||
|       departments.value = []; | ||||
|     } | ||||
|   }; | ||||
|   const formatHigherOptions = treeList => { | ||||
|     // 根据返回数据的status字段值判断追加是否禁用disabled字段,返回处理后的树结构,用于上级部门级联选择器的展示
 | ||||
|   const formatHigherOptions = (treeList: any) => { | ||||
|     // 根据返回数据的status字段值判断追加是否禁用disabled字段,返回处理后的树结构,用于上级部门级联选择器的展示(实际开发中也是如此,不可能前端需要的每个字段后端都会返回,这时需要前端自行根据后端返回的某些字段做逻辑处理)
 | ||||
|     if (!treeList || !treeList.length) return; | ||||
|     const newTreeList = []; | ||||
|     for (let i = 0; i < treeList.length; i++) { | ||||
| @ -379,6 +433,7 @@ export const useRole = (treeRef: Ref) => { | ||||
|     loading, | ||||
|     columns, | ||||
|     dataList, | ||||
|     selectedNum, | ||||
|     treeData, | ||||
|     treeProps, | ||||
|     isLinkage, | ||||
| @ -390,6 +445,8 @@ export const useRole = (treeRef: Ref) => { | ||||
|     rowStyle, | ||||
|     onSearch, | ||||
|     resetForm, | ||||
|     onSelectionCancel, | ||||
|     onbatchDel, | ||||
|     openDialog, | ||||
|     handleMenu, | ||||
|     handleSave, | ||||
| @ -398,6 +455,7 @@ export const useRole = (treeRef: Ref) => { | ||||
|     transformI18n, | ||||
|     onQueryChanged, | ||||
|     handleSizeChange, | ||||
|     handleCurrentChange | ||||
|     handleCurrentChange, | ||||
|     handleSelectionChange | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @ -16,6 +16,7 @@ import EditPen from "@iconify-icons/ep/edit-pen"; | ||||
| import Refresh from "@iconify-icons/ep/refresh"; | ||||
| import AddFill from "@iconify-icons/ri/add-circle-line"; | ||||
| import { onBeforeRouteUpdate } from "vue-router"; | ||||
| import { hasAuth } from "@/utils/auth"; | ||||
| defineOptions({ | ||||
|   name: "SystemUser" | ||||
| }); | ||||
| @ -117,6 +118,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|       <PureTableBar title="用户管理" :columns="columns" @refresh="onSearch"> | ||||
|         <template #buttons> | ||||
|           <el-button | ||||
|             v-if="hasAuth('user:btn:addUser')" | ||||
|             type="primary" | ||||
|             :icon="useRenderIcon(AddFill)" | ||||
|             @click="openDialog()" | ||||
| @ -141,7 +143,11 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|                 {{ t("buttons:Deselect") }} | ||||
|               </el-button> | ||||
|             </div> | ||||
|             <el-popconfirm title="是否确认删除?" @confirm="onbatchDel"> | ||||
|             <el-popconfirm | ||||
|               v-if="hasAuth('user:btn:deleteUser')" | ||||
|               title="是否确认删除?" | ||||
|               @confirm="onbatchDel" | ||||
|             > | ||||
|               <template #reference> | ||||
|                 <el-button type="danger" text class="mr-1"> | ||||
|                   {{ t("buttons:DeleteInBatches") }} | ||||
| @ -178,13 +184,14 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|                 link | ||||
|                 type="primary" | ||||
|                 :size="size" | ||||
|                 :disabled="!hasAuth('user:btn:updateUser')" | ||||
|                 :icon="useRenderIcon(EditPen)" | ||||
|                 @click="openDialog('修改', row)" | ||||
|               > | ||||
|                 {{ t("buttons:Update") }} | ||||
|               </el-button> | ||||
|               <el-popconfirm | ||||
|                 :title="`是否确认删除用户名称为${row.name}的这条数据?`" | ||||
|                 :title="`是否确认删除用户名称为${row.username}的这条数据?`" | ||||
|                 @confirm="handleDelete(row)" | ||||
|               > | ||||
|                 <template #reference> | ||||
| @ -193,6 +200,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|                     link | ||||
|                     type="danger" | ||||
|                     :size="size" | ||||
|                     :disabled="!hasAuth('user:btn:deleteUser')" | ||||
|                     :icon="useRenderIcon(Delete)" | ||||
|                   > | ||||
|                     {{ t("buttons:Delete") }} | ||||
| @ -216,6 +224,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|                         link | ||||
|                         type="primary" | ||||
|                         :size="size" | ||||
|                         :disabled="!hasAuth('user:btn:uploadAvatar')" | ||||
|                         :icon="useRenderIcon(Upload)" | ||||
|                         @click="handleUpload(row)" | ||||
|                       > | ||||
| @ -228,6 +237,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|                         link | ||||
|                         type="primary" | ||||
|                         :size="size" | ||||
|                         :disabled="!hasAuth('user:btn:reset_password')" | ||||
|                         :icon="useRenderIcon(Password)" | ||||
|                         @click="handleReset(row)" | ||||
|                       > | ||||
| @ -240,6 +250,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|                         link | ||||
|                         type="primary" | ||||
|                         :size="size" | ||||
|                         :disabled="!hasAuth('user:btn:updateRole')" | ||||
|                         :icon="useRenderIcon(Role)" | ||||
|                         @click="handleRole(row)" | ||||
|                       > | ||||
| @ -252,6 +263,7 @@ onBeforeRouteUpdate((to, from, next) => { | ||||
|                         link | ||||
|                         type="primary" | ||||
|                         :size="size" | ||||
|                         :disabled="!hasAuth('user:btn:permissionList')" | ||||
|                         :icon="useRenderIcon(Menu)" | ||||
|                         @click="openPerDialog(row)" | ||||
|                       > | ||||
|  | ||||
| @ -105,8 +105,8 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => { | ||||
|         <el-image | ||||
|           fit="cover" | ||||
|           preview-teleported={true} | ||||
|           src={row.avatar || Avatar} | ||||
|           preview-src-list={Array.of(row.avatar || Avatar)} | ||||
|           src={`/api/${row.avatar}` || Avatar} | ||||
|           preview-src-list={Array.of(`/api/${row.avatar}` || Avatar)} | ||||
|           class="w-[24px] h-[24px] rounded-full align-middle" | ||||
|         /> | ||||
|       ) | ||||
| @ -191,7 +191,7 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => { | ||||
|    */ | ||||
|   const handleDelete = async (row: UserInfo) => { | ||||
|     const res = await deleteUserAPI(row.id); | ||||
|     if (res.code === 200) { | ||||
|     if (res.success) { | ||||
|       message(`您删除了用户账号为${row.username}的这条数据`, { | ||||
|         type: "success" | ||||
|       }); | ||||
| @ -261,10 +261,10 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => { | ||||
|     // 返回当前选中的行
 | ||||
|     const curSelected = tableRef.value.getTableRef().getSelectionRows(); | ||||
|     const res = await deleteUserListAPI({ | ||||
|       userIds: getKeyList(curSelected, "id") | ||||
|       ids: getKeyList(curSelected, "id") | ||||
|     }); | ||||
|     if (res.code === 200) { | ||||
|       message(`已删除用户编号为 ${getKeyList(curSelected, "id")} 的数据`, { | ||||
|     if (res.success) { | ||||
|       message(res.msg, { | ||||
|         type: "success" | ||||
|       }); | ||||
|       tableRef.value.getTableRef().clearSelection(); | ||||
| @ -404,7 +404,7 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => { | ||||
|                 } | ||||
|               } | ||||
|               const res = await postAddUserAPI(addForm); | ||||
|               if (res.code === 200) { | ||||
|               if (res.success) { | ||||
|                 // 实际开发先调用新增接口,再进行下面操作
 | ||||
|                 chores(); | ||||
|               } else { | ||||
| @ -430,7 +430,7 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => { | ||||
|                 } | ||||
|               } | ||||
|               const res = await putUpdateUserAPI(curData.id, updateForm); | ||||
|               if (res.code === 200) { | ||||
|               if (res.success) { | ||||
|                 chores(); | ||||
|               } else { | ||||
|                 message(`更新失败!`, { type: "error" }); | ||||
| @ -453,14 +453,14 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => { | ||||
|       contentRenderer: () => | ||||
|         h(croppingUpload, { | ||||
|           ref: cropRef, | ||||
|           imgSrc: row.avatar || Avatar, | ||||
|           imgSrc: `/api/${row.avatar}` || Avatar, | ||||
|           onCropper: info => (avatarInfo.value = info) | ||||
|         }), | ||||
|       beforeSure: async done => { | ||||
|         const res = await postUploadAvatarAPI(row.id, { | ||||
|           file: avatarInfo.value.blob | ||||
|         }); | ||||
|         if (res.code === 200) { | ||||
|         if (res.success) { | ||||
|           // 根据实际业务使用avatarInfo.value和row里的某些字段去调用上传头像接口即可
 | ||||
|           message(`更新成功!`, { type: "success" }); | ||||
|           onSearch(); // 刷新表格数据
 | ||||
| @ -541,7 +541,7 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => { | ||||
|             const res = await putUpdateUserPasswordAPI(row.id, { | ||||
|               password: pwdForm.newPwd | ||||
|             }); | ||||
|             if (res.code === 200) { | ||||
|             if (res.success) { | ||||
|               console.log(pwdForm.newPwd); | ||||
|               done(); | ||||
|               message(`已成功重置 ${row.username} 的密码`, { | ||||
| @ -593,7 +593,7 @@ export const useUser = (tableRef: Ref, treeRef: Ref) => { | ||||
|           user_id: row.id, | ||||
|           role_ids: curData.ids as string[] | ||||
|         }); | ||||
|         if (res.code === 200) { | ||||
|         if (res.success) { | ||||
|           message(`${row.username}--${row.nickname}的角色信息更新成功!`, { | ||||
|             type: "success", | ||||
|             duration: 5000 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user