mirror of
				https://github.com/pure-admin/pure-admin-thin.git
				synced 2025-10-31 15:44:49 +08:00 
			
		
		
		
	feat: 新增角色管理、岗位管理
This commit is contained in:
		
							parent
							
								
									6719fd8290
								
							
						
					
					
						commit
						b068518ba6
					
				| @ -1,4 +1,5 @@ | ||||
| import { http } from "@/utils/http"; | ||||
| import { Tree } from "@/utils/tree"; | ||||
| 
 | ||||
| export interface MenuQuery { | ||||
|   isButton: boolean; | ||||
| @ -7,7 +8,7 @@ export interface MenuQuery { | ||||
| /** | ||||
|  * MenuDTO | ||||
|  */ | ||||
| export interface MenuDTO { | ||||
| export interface MenuDTO extends Tree { | ||||
|   createTime?: Date; | ||||
|   isButton?: number; | ||||
|   id?: number; | ||||
|  | ||||
							
								
								
									
										70
									
								
								src/api/system/post.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/api/system/post.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | ||||
| import { http } from "@/utils/http"; | ||||
| 
 | ||||
| export interface PostListCommand extends BasePageQuery { | ||||
|   postCode?: string; | ||||
|   postName?: string; | ||||
|   status?: number; | ||||
| } | ||||
| 
 | ||||
| export interface PostPageResponse { | ||||
|   createTime: string; | ||||
|   postCode: string; | ||||
|   postId: number; | ||||
|   postName: string; | ||||
|   postSort: number; | ||||
|   remark: string; | ||||
|   status: number; | ||||
|   statusStr: string; | ||||
| } | ||||
| 
 | ||||
| export function getPostListApi(params: PostListCommand) { | ||||
|   return http.request<ResponseData<PageDTO<PostPageResponse>>>( | ||||
|     "get", | ||||
|     "/system/post/list", | ||||
|     { | ||||
|       params | ||||
|     } | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export const exportPostExcelApi = ( | ||||
|   params: PostListCommand, | ||||
|   fileName: string | ||||
| ) => { | ||||
|   return http.download("/system/post/excel", fileName, { | ||||
|     params | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| export const deletePostApi = (data: Array<number>) => { | ||||
|   return http.request<ResponseData<void>>("delete", "/system/post", { | ||||
|     params: { | ||||
|       // 需要将数组转换为字符串  否则Axios会将参数变成 noticeIds[0]:1  noticeIds[1]:2 这种格式,后端接收参数不成功
 | ||||
|       ids: data.toString() | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| export interface AddPostCommand { | ||||
|   postCode: string; | ||||
|   postName: string; | ||||
|   postSort: number; | ||||
|   remark?: string; | ||||
|   status?: string; | ||||
| } | ||||
| 
 | ||||
| export const addPostApi = (data: AddPostCommand) => { | ||||
|   return http.request<ResponseData<void>>("post", "/system/post", { | ||||
|     data | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| export interface UpdatePostCommand extends AddPostCommand { | ||||
|   postId: number; | ||||
| } | ||||
| 
 | ||||
| export const updatePostApi = (data: UpdatePostCommand) => { | ||||
|   return http.request<ResponseData<void>>("put", "/system/post", { | ||||
|     data | ||||
|   }); | ||||
| }; | ||||
							
								
								
									
										65
									
								
								src/api/system/role.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/api/system/role.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,65 @@ | ||||
| import { http } from "@/utils/http"; | ||||
| 
 | ||||
| export interface RoleQuery extends BasePageQuery { | ||||
|   roleKey?: string; | ||||
|   roleName?: string; | ||||
|   status?: string; | ||||
|   timeRangeColumn?: string; | ||||
| } | ||||
| 
 | ||||
| export interface RoleDTO { | ||||
|   createTime: Date; | ||||
|   dataScope: number; | ||||
|   remark: string; | ||||
|   roleId: number; | ||||
|   roleKey: string; | ||||
|   roleName: string; | ||||
|   roleSort: number; | ||||
|   selectedDeptList: number[]; | ||||
|   selectedMenuList: number[]; | ||||
|   status: number; | ||||
| } | ||||
| 
 | ||||
| export function getRoleListApi(params: RoleQuery) { | ||||
|   return http.request<ResponseData<PageDTO<RoleDTO>>>( | ||||
|     "get", | ||||
|     "/system/role/list", | ||||
|     { | ||||
|       params | ||||
|     } | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export function getRoleInfoApi(roleId: number) { | ||||
|   return http.request<ResponseData<RoleDTO>>("get", "/system/role/" + roleId); | ||||
| } | ||||
| 
 | ||||
| export interface AddRoleCommand { | ||||
|   dataScope?: string; | ||||
|   menuIds: number[]; | ||||
|   remark?: string; | ||||
|   roleKey: string; | ||||
|   roleName: string; | ||||
|   roleSort: number; | ||||
|   status?: string; | ||||
| } | ||||
| 
 | ||||
| export function addRoleApi(data: AddRoleCommand) { | ||||
|   return http.request<void>("post", "/system/role", { | ||||
|     data | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| export interface UpdateRoleCommand extends AddRoleCommand { | ||||
|   roleId: number; | ||||
| } | ||||
| 
 | ||||
| export function updateRoleApi(data: UpdateRoleCommand) { | ||||
|   return http.request<void>("put", "/system/role", { | ||||
|     data | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| export function deleteRoleApi(roleId: number) { | ||||
|   return http.request<void>("delete", "/system/role/" + roleId); | ||||
| } | ||||
							
								
								
									
										1
									
								
								src/assets/svg/FullScreenMaximize.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/assets/svg/FullScreenMaximize.svg
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 20 20"><g fill="none"><path d="M3 5a2 2 0 0 1 2-2h2a.5.5 0 0 1 0 1H5a1 1 0 0 0-1 1v2a.5.5 0 0 1-1 0V5zm9.5-1.5A.5.5 0 0 1 13 3h2a2 2 0 0 1 2 2v2a.5.5 0 0 1-1 0V5a1 1 0 0 0-1-1h-2a.5.5 0 0 1-.5-.5zm-9 9a.5.5 0 0 1 .5.5v2a1 1 0 0 0 1 1h2a.5.5 0 0 1 0 1H5a2 2 0 0 1-2-2v-2a.5.5 0 0 1 .5-.5zm13 0a.5.5 0 0 1 .5.5v2a2 2 0 0 1-2 2h-2a.5.5 0 0 1 0-1h2a1 1 0 0 0 1-1v-2a.5.5 0 0 1 .5-.5z" fill="currentColor"/></g></svg> | ||||
| After Width: | Height: | Size: 508 B | 
							
								
								
									
										1
									
								
								src/assets/svg/FullScreenMinimize.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/assets/svg/FullScreenMinimize.svg
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g fill="none"><path d="M8.5 3.75a.75.75 0 0 0-1.5 0v2.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 0 0 1.5h2.5A2.25 2.25 0 0 0 8.5 6.25v-2.5zm0 16.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 0-.75-.75h-2.5a.75.75 0 0 1 0-1.5h2.5a2.25 2.25 0 0 1 2.25 2.25v2.5zM16.25 3a.75.75 0 0 0-.75.75v2.5a2.25 2.25 0 0 0 2.25 2.25h2.5a.75.75 0 0 0 0-1.5h-2.5a.75.75 0 0 1-.75-.75v-2.5a.75.75 0 0 0-.75-.75zm-.75 17.25a.75.75 0 0 0 1.5 0v-2.5a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 0 0-1.5h-2.5a2.25 2.25 0 0 0-2.25 2.25v2.5z" fill="currentColor"/></g></svg> | ||||
| After Width: | Height: | Size: 631 B | 
							
								
								
									
										173
									
								
								src/components/VDialog/VDialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								src/components/VDialog/VDialog.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,173 @@ | ||||
| <template> | ||||
|   <el-dialog | ||||
|     v-model="visible" | ||||
|     :fullscreen="fullScreen" | ||||
|     class="v-dialog" | ||||
|     :class="dialogClazz" | ||||
|     :draggable="props.draggable" | ||||
|     :show-close="false" | ||||
|     v-bind="$attrs" | ||||
|   > | ||||
|     <template #header> | ||||
|       <slot name="header"> | ||||
|         <div | ||||
|           style=" | ||||
|             position: relative; | ||||
|             box-sizing: border-box; | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             justify-content: center; | ||||
|             height: 24px; | ||||
|             font-weight: bold; | ||||
|             color: rgb(118 131 164); | ||||
|           " | ||||
|         > | ||||
|           <div v-text="props.title" /> | ||||
|           <div | ||||
|             style=" | ||||
|               position: absolute; | ||||
|               right: 0; | ||||
|               display: flex; | ||||
|               flex-direction: row; | ||||
|               justify-content: center; | ||||
|             " | ||||
|           > | ||||
|             <el-button | ||||
|               v-if="props.showFullScreen" | ||||
|               :icon="fullScreen ? FullScreenMinimize : FullScreenMaximize" | ||||
|               link | ||||
|               @click="requestFullScreen" | ||||
|               class="header-btn" | ||||
|             /> | ||||
|             <el-button | ||||
|               :icon="Close" | ||||
|               link | ||||
|               @click="handleCloseClick" | ||||
|               class="header-btn" | ||||
|               style="margin-left: 0" | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </slot> | ||||
|     </template> | ||||
|     <template v-if="useBodyScrolling"> | ||||
|       <el-scrollbar :max-height="bodyHeight" always> | ||||
|         <slot name="default" /> | ||||
|       </el-scrollbar> | ||||
|     </template> | ||||
|     <template v-else> | ||||
|       <slot name="default" /> | ||||
|     </template> | ||||
| 
 | ||||
|     <template #footer> | ||||
|       <slot name="footer" v-if="!props.hiddenFooter"> | ||||
|         <div style="display: flex; justify-content: center"> | ||||
|           <el-button | ||||
|             :loading="props.loading" | ||||
|             type="primary" | ||||
|             @click="handleConfirm" | ||||
|             >{{ props.confirmText }}</el-button | ||||
|           > | ||||
|           <el-button :loading="props.loading" @click="handleCancel">{{ | ||||
|             cancelText | ||||
|           }}</el-button> | ||||
|         </div> | ||||
|       </slot> | ||||
|     </template> | ||||
|   </el-dialog> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { computed, ref } from "vue"; | ||||
| import { ElDialog, ElButton, ElScrollbar } from "element-plus"; | ||||
| import { DialogEmits, DialogProps } from "./dialog"; | ||||
| import { Close } from "@element-plus/icons-vue"; | ||||
| import FullScreenMaximize from "@/assets/svg/FullScreenMaximize.svg?component"; | ||||
| import FullScreenMinimize from "@/assets/svg/FullScreenMinimize.svg?component"; | ||||
| 
 | ||||
| const props = withDefaults(defineProps<DialogProps>(), { | ||||
|   fullScreen: undefined, | ||||
|   confirmText: "确定", | ||||
|   cancelText: "取消", | ||||
|   disableFooter: false, | ||||
|   useBodyScrolling: false, | ||||
|   fixedBodyHeight: true, | ||||
|   draggable: true, | ||||
|   loading: false | ||||
| }); | ||||
| const emits = defineEmits<DialogEmits>(); | ||||
| 
 | ||||
| const visible = computed<boolean>({ | ||||
|   get: () => { | ||||
|     return props.modelValue; | ||||
|   }, | ||||
|   set: v => emits("update:modelValue", v) | ||||
| }); | ||||
| 
 | ||||
| const fullScreenState = ref(!!props.initFullScreen); | ||||
| const fullScreen = computed<boolean>({ | ||||
|   get: () => { | ||||
|     console.log("fullScreen getter", props.fullScreen, fullScreenState.value); | ||||
|     // 非受控模式,状态完全由组件内部控制 | ||||
|     if (props.fullScreen === undefined) { | ||||
|       return fullScreenState.value; | ||||
|     } else { | ||||
|       return props.fullScreen; | ||||
|     } | ||||
|   }, | ||||
|   set: v => { | ||||
|     fullScreenState.value = v; | ||||
|     console.log("fullScreen setter", v, props.fullScreen); | ||||
|     // 受控模式,将状态更新到父组件 | ||||
|     if (props.fullScreen !== undefined) { | ||||
|       emits("update:fullScreen", v); | ||||
|     } | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| // const fullScreen = ref<boolean>(false) | ||||
| function requestFullScreen() { | ||||
|   fullScreen.value = !fullScreen.value; | ||||
| } | ||||
| 
 | ||||
| const bodyHeight = computed(() => { | ||||
|   const footerHeight = props.hiddenFooter ? "0" : "52px"; | ||||
|   if (props.fullScreen) { | ||||
|     // footerHeight=52,headerHeight=44,padding=12 | ||||
|     return `calc(100dvh - ${footerHeight} - 44px)`; | ||||
|   } else { | ||||
|     return `calc(70dvh - ${footerHeight} - 44px)`; | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| const dialogClazz = computed(() => { | ||||
|   const classList: string[] = ["v-dialog"]; | ||||
|   if (!props.fixedBodyHeight) { | ||||
|     classList.push("flex-body"); | ||||
|   } | ||||
|   if (props.hiddenFooter) { | ||||
|     classList.push("hidden-footer"); | ||||
|   } | ||||
|   return classList; | ||||
| }); | ||||
| 
 | ||||
| function handleConfirm() { | ||||
|   emits("confirm"); | ||||
| } | ||||
| 
 | ||||
| function handleCancel() { | ||||
|   emits("cancel"); | ||||
| } | ||||
| 
 | ||||
| function handleCloseClick() { | ||||
|   visible.value = false; | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| .header-btn :deep(.el-icon), | ||||
| .header-btn :deep(.el-icon svg) { | ||||
|   width: 24px; | ||||
|   height: 24px; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										62
									
								
								src/components/VDialog/dialog.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/components/VDialog/dialog.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| html.dark .v-dialog { | ||||
|   --header-bg-color: #171d1e; | ||||
|   --footer-bg-color: #171d1e; | ||||
| } | ||||
| 
 | ||||
| .v-dialog { | ||||
|   --header-bg-color: #f5f7fa; | ||||
|   --footer-bg-color: #f5f7fa; | ||||
| } | ||||
| 
 | ||||
| .v-dialog.el-dialog.hidden-footer .el-dialog__footer { | ||||
|   padding: 0; | ||||
|   border: none; | ||||
| } | ||||
| 
 | ||||
| .el-dialog__footer { | ||||
|   padding: 10px; | ||||
|   /*border-top: 1px solid var(--el-border-color);*/ | ||||
|   /*border-bottom: 1px solid var(--el-border-color);*/ | ||||
|   box-sizing: border-box; | ||||
|   background-color: var(--header-bg-color); | ||||
|   position: relative; /* 防止被表单覆盖底部 */ | ||||
|   z-index: calc(var(--el-index-normal) + 1); | ||||
| } | ||||
| 
 | ||||
| .v-dialog.el-dialog { | ||||
|   box-sizing: border-box; | ||||
|   margin: 15dvh auto; | ||||
| } | ||||
| 
 | ||||
| .v-dialog.el-dialog.is-fullscreen { | ||||
|   box-sizing: border-box; | ||||
|   margin: auto; | ||||
| } | ||||
| 
 | ||||
| .v-dialog.el-dialog .el-dialog__header { | ||||
|   padding: 10px 16px; | ||||
|   /*border-bottom: 1px solid var(--el-border-color);*/ | ||||
|   margin-right: 0; | ||||
|   box-sizing: border-box; | ||||
|   background-color: var(--header-bg-color); | ||||
| } | ||||
| 
 | ||||
| .v-dialog.el-dialog--center .el-dialog__body, | ||||
| .el-dialog__body { | ||||
|   padding: 16px 20px; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| .v-dialog.el-dialog.is-fullscreen .el-dialog__body { | ||||
|   height: calc(100dvh - 44px - 52px); | ||||
|   max-height: calc(100dvh - 44px - 52px); | ||||
| } | ||||
| 
 | ||||
| .v-dialog.el-dialog .el-dialog__body { | ||||
|   height: calc(70dvh - 44px - 52px); | ||||
| } | ||||
| 
 | ||||
| .v-dialog.el-dialog.flex-body:not(.is-fullscreen) .el-dialog__body { | ||||
|   height: initial; | ||||
|   max-height: calc(70dvh - 44px - 52px); | ||||
| } | ||||
							
								
								
									
										47
									
								
								src/components/VDialog/dialog.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/components/VDialog/dialog.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| export interface DialogProps { | ||||
|   /** | ||||
|    * 标题 | ||||
|    */ | ||||
|   title: string; | ||||
|   /** | ||||
|    * 显隐 | ||||
|    */ | ||||
|   modelValue: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * 初始化全屏状态 | ||||
|    */ | ||||
|   initFullScreen?: boolean | undefined; | ||||
| 
 | ||||
|   /** | ||||
|    * 展示全屏按钮 | ||||
|    */ | ||||
|   showFullScreen?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * 全屏 | ||||
|    */ | ||||
|   fullScreen?: boolean | undefined; | ||||
|   confirmText?: string; | ||||
|   cancelText?: string; | ||||
|   loading?: boolean; | ||||
|   /** | ||||
|    * 使用el-scrollbar包裹对话框body | ||||
|    */ | ||||
|   useBodyScrolling?: boolean; | ||||
|   /** | ||||
|    * 固定对话框body高度 | ||||
|    */ | ||||
|   fixedBodyHeight?: boolean; | ||||
| 
 | ||||
|   draggable?: boolean; | ||||
| 
 | ||||
|   hiddenFooter?: boolean; | ||||
| } | ||||
| 
 | ||||
| export type DialogEmits = { | ||||
|   "update:modelValue": [val: boolean]; | ||||
|   "update:fullScreen": [val: boolean]; | ||||
|   confirm: []; | ||||
|   cancel: []; | ||||
| }; | ||||
| @ -21,6 +21,7 @@ import "element-plus/dist/index.css"; | ||||
| // 导入字体图标
 | ||||
| import "./assets/iconfont/iconfont.js"; | ||||
| import "./assets/iconfont/iconfont.css"; | ||||
| import "@/components/VDialog/dialog.css"; | ||||
| 
 | ||||
| const app = createApp(App); | ||||
| 
 | ||||
|  | ||||
| @ -42,5 +42,5 @@ export type userType = { | ||||
|   /** 字典ListMap 用于下拉框直接展示 */ | ||||
|   dictionaryList: Map<String, Array<DictionaryData>>; | ||||
|   /** 字典MapMap 用于匹配值展示 */ | ||||
|   dictionaryMap: Map<String, Map<String, DictionaryData>>; | ||||
|   dictionaryMap: Record<string, Record<string, DictionaryData>>; | ||||
| }; | ||||
|  | ||||
| @ -205,3 +205,25 @@ export const handleTree = ( | ||||
|   } | ||||
|   return tree; | ||||
| }; | ||||
| 
 | ||||
| export interface Tree { | ||||
|   children?: this[]; | ||||
| } | ||||
| 
 | ||||
| export function toTree<T extends Tree>( | ||||
|   src: T[], | ||||
|   keyField: keyof T, | ||||
|   parentField: keyof T | ||||
| ): T[] { | ||||
|   const map = new Map<unknown, T>(src.map(it => [it[keyField], it])); | ||||
|   src.forEach(it => { | ||||
|     if (map.has(it[parentField])) { | ||||
|       const parent = map.get(it[parentField])!; | ||||
|       if (!parent.children) { | ||||
|         parent.children = []; | ||||
|       } | ||||
|       parent.children.push(it); | ||||
|     } | ||||
|   }); | ||||
|   return src.filter(it => !it[parentField]); | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| <script setup lang="ts"> | ||||
| import { ref } from "vue"; | ||||
| import { useLoginLogHook } from "./utils/hook"; | ||||
| import { usePostHook } from "./utils/hook"; | ||||
| import { PureTableBar } from "@/components/RePureTableBar"; | ||||
| import { useRenderIcon } from "@/components/ReIcon/src/hooks"; | ||||
| 
 | ||||
| @ -10,14 +10,17 @@ import Refresh from "@iconify-icons/ep/refresh"; | ||||
| import { useUserStoreHook } from "@/store/modules/user"; | ||||
| // TODO 这个导入声明好长  看看如何优化 | ||||
| import { CommonUtils } from "@/utils/common"; | ||||
| import PostFormModal from "@/views/system/post/post-form-modal.vue"; | ||||
| import EditPen from "@iconify-icons/ep/edit-pen"; | ||||
| import { PostPageResponse } from "@/api/system/post"; | ||||
| import AddFill from "@iconify-icons/ri/add-circle-line"; | ||||
| 
 | ||||
| /** 组件name最好和菜单表中的router_name一致 */ | ||||
| defineOptions({ | ||||
|   name: "SystemOperationLog" | ||||
|   name: "Post" | ||||
| }); | ||||
| 
 | ||||
| const loginLogStatusList = | ||||
|   useUserStoreHook().dictionaryList["sysLoginLog.status"]; | ||||
| const loginLogStatusList = useUserStoreHook().dictionaryList["common.status"]; | ||||
| 
 | ||||
| const tableRef = ref(); | ||||
| 
 | ||||
| @ -33,11 +36,21 @@ const { | ||||
|   multipleSelection, | ||||
|   onSearch, | ||||
|   resetForm, | ||||
|   onSortChanged, | ||||
|   exportAllExcel, | ||||
|   getLoginLogList, | ||||
|   getPostList, | ||||
|   handleDelete, | ||||
|   handleBulkDelete | ||||
| } = useLoginLogHook(); | ||||
| } = usePostHook(); | ||||
| 
 | ||||
| const opType = ref<"add" | "update">("add"); | ||||
| const modalVisible = ref(false); | ||||
| const opRow = ref<PostPageResponse>(); | ||||
| function openDialog(type: "add" | "update", row?: PostPageResponse) { | ||||
|   opType.value = type; | ||||
|   opRow.value = row; | ||||
|   modalVisible.value = true; | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
| @ -49,18 +62,18 @@ const { | ||||
|       :model="searchFormParams" | ||||
|       class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]" | ||||
|     > | ||||
|       <el-form-item label="登录IP:" prop="ipAddress"> | ||||
|       <el-form-item label="岗位编码" prop="postCode"> | ||||
|         <el-input | ||||
|           v-model="searchFormParams.ipAddress" | ||||
|           placeholder="请输入IP地址" | ||||
|           v-model="searchFormParams.postCode" | ||||
|           placeholder="请输入岗位编码" | ||||
|           clearable | ||||
|           class="!w-[200px]" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="用户名:" prop="username"> | ||||
|       <el-form-item label="岗位名称" prop="postName"> | ||||
|         <el-input | ||||
|           v-model="searchFormParams.username" | ||||
|           placeholder="请选择用户名称" | ||||
|           v-model="searchFormParams.postName" | ||||
|           placeholder="请选择岗位名称" | ||||
|           clearable | ||||
|           class="!w-[200px]" | ||||
|         /> | ||||
| @ -81,11 +94,7 @@ const { | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
|         <label class="el-form-item__label is-required font-bold" | ||||
|           >登录时间:</label | ||||
|         > | ||||
|         <!-- TODO 如何消除这个v-model的warning --> | ||||
|       <el-form-item label="创建时间"> | ||||
|         <el-date-picker | ||||
|           class="!w-[240px]" | ||||
|           v-model="timeRange" | ||||
| @ -115,9 +124,16 @@ const { | ||||
|     </el-form> | ||||
| 
 | ||||
|     <!-- table bar 包裹  table --> | ||||
|     <PureTableBar title="登录日志列表" :columns="columns" @refresh="onSearch"> | ||||
|     <PureTableBar title="岗位列表" :columns="columns" @refresh="onSearch"> | ||||
|       <!-- 表格操作栏 --> | ||||
|       <template #buttons> | ||||
|         <el-button | ||||
|           type="primary" | ||||
|           :icon="useRenderIcon(AddFill)" | ||||
|           @click="openDialog('add')" | ||||
|         > | ||||
|           新增岗位 | ||||
|         </el-button> | ||||
|         <el-button | ||||
|           type="danger" | ||||
|           :icon="useRenderIcon(Delete)" | ||||
| @ -127,7 +143,7 @@ const { | ||||
|         </el-button> | ||||
|         <el-button | ||||
|           type="primary" | ||||
|           @click="CommonUtils.exportExcel(columns, dataList, '登录日志列表')" | ||||
|           @click="CommonUtils.exportExcel(columns, dataList, '岗位列表')" | ||||
|           >单页导出</el-button | ||||
|         > | ||||
|         <el-button type="primary" @click="exportAllExcel">全部导出</el-button> | ||||
| @ -151,16 +167,26 @@ const { | ||||
|             background: 'var(--el-table-row-hover-bg-color)', | ||||
|             color: 'var(--el-text-color-primary)' | ||||
|           }" | ||||
|           @page-size-change="getLoginLogList" | ||||
|           @page-current-change="getLoginLogList" | ||||
|           @sort-change="getLoginLogList" | ||||
|           @page-size-change="getPostList" | ||||
|           @page-current-change="getPostList" | ||||
|           @sort-change="onSortChanged" | ||||
|           @selection-change=" | ||||
|             rows => (multipleSelection = rows.map(item => item.logId)) | ||||
|             rows => (multipleSelection = rows.map(item => item.postId)) | ||||
|           " | ||||
|         > | ||||
|           <template #operation="{ row }"> | ||||
|             <el-button | ||||
|               class="reset-margin" | ||||
|               link | ||||
|               type="primary" | ||||
|               :size="size" | ||||
|               :icon="useRenderIcon(EditPen)" | ||||
|               @click="openDialog('update', row)" | ||||
|             > | ||||
|               编辑 | ||||
|             </el-button> | ||||
|             <el-popconfirm | ||||
|               :title="`是否确认删除编号为${row.logId}的这条日志`" | ||||
|               :title="`是否确认删除编号为${row.postId}的这个岗位`" | ||||
|               @confirm="handleDelete(row)" | ||||
|             > | ||||
|               <template #reference> | ||||
| @ -179,6 +205,13 @@ const { | ||||
|         </pure-table> | ||||
|       </template> | ||||
|     </PureTableBar> | ||||
| 
 | ||||
|     <post-form-modal | ||||
|       v-model="modalVisible" | ||||
|       :type="opType" | ||||
|       :row="opRow" | ||||
|       @success="onSearch" | ||||
|     /> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										131
									
								
								src/views/system/post/post-form-modal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/views/system/post/post-form-modal.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | ||||
| <script setup lang="ts"> | ||||
| import VDialog from "@/components/VDialog/VDialog.vue"; | ||||
| import { computed, reactive, ref } from "vue"; | ||||
| import { | ||||
|   AddPostCommand, | ||||
|   PostPageResponse, | ||||
|   UpdatePostCommand, | ||||
|   addPostApi, | ||||
|   updatePostApi | ||||
| } from "@/api/system/post"; | ||||
| import { useUserStoreHook } from "@/store/modules/user"; | ||||
| import { ElMessage, FormInstance, FormRules } from "element-plus"; | ||||
| 
 | ||||
| interface Props { | ||||
|   type: "add" | "update"; | ||||
|   modelValue: boolean; | ||||
|   row?: PostPageResponse; | ||||
| } | ||||
| 
 | ||||
| const props = defineProps<Props>(); | ||||
| const emits = defineEmits<{ | ||||
|   (e: "update:modelValue", v: boolean): void; | ||||
|   (e: "success"): void; | ||||
| }>(); | ||||
| 
 | ||||
| const visible = computed({ | ||||
|   get: () => props.modelValue, | ||||
|   set(v) { | ||||
|     emits("update:modelValue", v); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| const formData = reactive<AddPostCommand | UpdatePostCommand>({ | ||||
|   postId: 0, | ||||
|   postCode: "", | ||||
|   postName: "", | ||||
|   postSort: 1, | ||||
|   remark: "", | ||||
|   status: "" | ||||
| }); | ||||
| 
 | ||||
| const statusList = useUserStoreHook().dictionaryMap["common.status"]; | ||||
| 
 | ||||
| const rules: FormRules = { | ||||
|   postName: [ | ||||
|     { | ||||
|       required: true, | ||||
|       message: "岗位名称不能为空" | ||||
|     } | ||||
|   ], | ||||
|   postCode: [ | ||||
|     { | ||||
|       required: true, | ||||
|       message: "岗位编码不能为空" | ||||
|     } | ||||
|   ], | ||||
|   postSort: [ | ||||
|     { | ||||
|       required: true, | ||||
|       message: "岗位序号不能为空" | ||||
|     } | ||||
|   ] | ||||
| }; | ||||
| const formRef = ref<FormInstance>(); | ||||
| function handleOpened() { | ||||
|   if (props.row) { | ||||
|     Object.assign(formData, props.row); | ||||
|   } else { | ||||
|     formRef.value?.resetFields(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const loading = ref(false); | ||||
| async function handleConfirm() { | ||||
|   try { | ||||
|     loading.value = true; | ||||
|     if (props.type === "add") { | ||||
|       await addPostApi(formData); | ||||
|     } else if (props.type === "update") { | ||||
|       await updatePostApi(formData as UpdatePostCommand); | ||||
|     } | ||||
|     ElMessage.info("提交成功"); | ||||
|     visible.value = false; | ||||
|     emits("success"); | ||||
|   } catch (e) { | ||||
|     console.error(e); | ||||
|     ElMessage.error((e as Error)?.message || "提交失败"); | ||||
|   } finally { | ||||
|     loading.value = false; | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <v-dialog | ||||
|     show-full-screen | ||||
|     :fixed-body-height="false" | ||||
|     use-body-scrolling | ||||
|     :title="type === 'add' ? '新增岗位' : '更新岗位'" | ||||
|     v-model="visible" | ||||
|     :loading="loading" | ||||
|     @confirm="handleConfirm" | ||||
|     @cancel="visible = false" | ||||
|     @opened="handleOpened" | ||||
|   > | ||||
|     <el-form :model="formData" label-width="120px" :rules="rules" ref="formRef"> | ||||
|       <el-form-item prop="postName" label="岗位名称" required inline-message> | ||||
|         <el-input v-model="formData.postName" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item prop="postCode" label="岗位编码" required> | ||||
|         <el-input v-model="formData.postCode" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item prop="postSort" label="岗位顺序" required> | ||||
|         <el-input-number :min="1" v-model="formData.postSort" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item prop="status" label="岗位状态"> | ||||
|         <el-radio-group v-model="formData.status"> | ||||
|           <el-radio | ||||
|             v-for="item in Object.keys(statusList)" | ||||
|             :key="item" | ||||
|             :label="statusList[item].value" | ||||
|             >{{ statusList[item].label }}</el-radio | ||||
|           > | ||||
|         </el-radio-group> | ||||
|       </el-form-item> | ||||
|       <el-form-item prop="remark" label="备注" style="margin-bottom: 0"> | ||||
|         <el-input type="textarea" v-model="formData.remark" /> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|   </v-dialog> | ||||
| </template> | ||||
| @ -1,23 +1,22 @@ | ||||
| import dayjs from "dayjs"; | ||||
| import { message } from "@/utils/message"; | ||||
| import { ElMessageBox, Sort } from "element-plus"; | ||||
| import { | ||||
|   getLoginLogListApi, | ||||
|   deleteLoginLogApi, | ||||
|   exportLoginLogExcelApi, | ||||
|   LoginLogQuery | ||||
| } from "@/api/system/log"; | ||||
| import { reactive, ref, onMounted, toRaw } from "vue"; | ||||
| import { reactive, ref, onMounted, toRaw, computed } from "vue"; | ||||
| import { useUserStoreHook } from "@/store/modules/user"; | ||||
| import { CommonUtils } from "@/utils/common"; | ||||
| import { PaginationProps } from "@pureadmin/table"; | ||||
| import { | ||||
|   PostListCommand, | ||||
|   getPostListApi, | ||||
|   exportPostExcelApi, | ||||
|   deletePostApi | ||||
| } from "@/api/system/post"; | ||||
| 
 | ||||
| const loginLogStatusMap = | ||||
|   useUserStoreHook().dictionaryMap["sysLoginLog.status"]; | ||||
| const statusMap = useUserStoreHook().dictionaryMap["common.status"]; | ||||
| 
 | ||||
| export function useLoginLogHook() { | ||||
| export function usePostHook() { | ||||
|   const defaultSort: Sort = { | ||||
|     prop: "loginTime", | ||||
|     prop: "createTime", | ||||
|     order: "descending" | ||||
|   }; | ||||
| 
 | ||||
| @ -28,20 +27,35 @@ export function useLoginLogHook() { | ||||
|     background: true | ||||
|   }; | ||||
| 
 | ||||
|   const timeRange = ref([]); | ||||
|   const timeRange = computed<[string, string] | null>({ | ||||
|     get() { | ||||
|       if (searchFormParams.beginTime && searchFormParams.endTime) { | ||||
|         return [searchFormParams.beginTime, searchFormParams.endTime]; | ||||
|       } else { | ||||
|         return null; | ||||
|       } | ||||
|     }, | ||||
|     set(v) { | ||||
|       if (v?.length === 2) { | ||||
|         searchFormParams.beginTime = v[0]; | ||||
|         searchFormParams.endTime = v[1]; | ||||
|       } else { | ||||
|         searchFormParams.beginTime = undefined; | ||||
|         searchFormParams.endTime = undefined; | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   const searchFormParams = reactive<LoginLogQuery>({ | ||||
|     ipAddress: undefined, | ||||
|     username: undefined, | ||||
|     status: undefined, | ||||
|     beginTime: undefined, | ||||
|     endTime: undefined, | ||||
|     timeRangeColumn: defaultSort.prop | ||||
|   const searchFormParams = reactive<PostListCommand>({ | ||||
|     postCode: "", | ||||
|     postName: "", | ||||
|     status: undefined | ||||
|   }); | ||||
| 
 | ||||
|   const dataList = ref([]); | ||||
|   const pageLoading = ref(true); | ||||
|   const multipleSelection = ref([]); | ||||
|   const sortState = ref<Sort>(defaultSort); | ||||
| 
 | ||||
|   const columns: TableColumnList = [ | ||||
|     { | ||||
| @ -49,34 +63,23 @@ export function useLoginLogHook() { | ||||
|       align: "left" | ||||
|     }, | ||||
|     { | ||||
|       label: "日志编号", | ||||
|       prop: "logId", | ||||
|       label: "岗位编号", | ||||
|       prop: "postId", | ||||
|       minWidth: 100 | ||||
|     }, | ||||
|     { | ||||
|       label: "用户名", | ||||
|       prop: "username", | ||||
|       minWidth: 120, | ||||
|       sortable: "custom" | ||||
|     }, | ||||
|     { | ||||
|       label: "IP地址", | ||||
|       prop: "ipAddress", | ||||
|       label: "岗位编码", | ||||
|       prop: "postCode", | ||||
|       minWidth: 120 | ||||
|     }, | ||||
|     { | ||||
|       label: "登录地点", | ||||
|       prop: "loginLocation", | ||||
|       label: "岗位名称", | ||||
|       prop: "postName", | ||||
|       minWidth: 120 | ||||
|     }, | ||||
|     { | ||||
|       label: "操作系统", | ||||
|       prop: "operationSystem", | ||||
|       minWidth: 120 | ||||
|     }, | ||||
|     { | ||||
|       label: "浏览器", | ||||
|       prop: "browser", | ||||
|       label: "岗位排序", | ||||
|       prop: "postSort", | ||||
|       minWidth: 120 | ||||
|     }, | ||||
|     { | ||||
| @ -86,26 +89,20 @@ export function useLoginLogHook() { | ||||
|       cellRenderer: ({ row, props }) => ( | ||||
|         <el-tag | ||||
|           size={props.size} | ||||
|           type={loginLogStatusMap[row.status].cssTag} | ||||
|           type={statusMap[row.status].cssTag} | ||||
|           effect="plain" | ||||
|         > | ||||
|           {loginLogStatusMap[row.status].label} | ||||
|           {statusMap[row.status].label} | ||||
|         </el-tag> | ||||
|       ) | ||||
|     }, | ||||
|     { | ||||
|       label: "状态名", | ||||
|       prop: "statusStr", | ||||
|       minWidth: 120, | ||||
|       hide: true | ||||
|     }, | ||||
|     { | ||||
|       label: "登录时间", | ||||
|       label: "创建时间", | ||||
|       minWidth: 160, | ||||
|       prop: "loginTime", | ||||
|       prop: "createTime", | ||||
|       sortable: "custom", | ||||
|       formatter: ({ loginTime }) => | ||||
|         dayjs(loginTime).format("YYYY-MM-DD HH:mm:ss") | ||||
|       formatter: ({ createTime }) => | ||||
|         dayjs(createTime).format("YYYY-MM-DD HH:mm:ss") | ||||
|     }, | ||||
|     { | ||||
|       label: "操作", | ||||
| @ -115,10 +112,15 @@ export function useLoginLogHook() { | ||||
|     } | ||||
|   ]; | ||||
| 
 | ||||
|   function onSortChanged(sort: Sort) { | ||||
|     sortState.value = sort; | ||||
|     onSearch(); | ||||
|   } | ||||
| 
 | ||||
|   async function onSearch() { | ||||
|     // 点击搜索的时候 需要重置分页
 | ||||
|     pagination.currentPage = 1; | ||||
|     getLoginLogList(); | ||||
|     getPostList(); | ||||
|   } | ||||
| 
 | ||||
|   function resetForm(formEl, tableRef) { | ||||
| @ -130,7 +132,6 @@ export function useLoginLogHook() { | ||||
|     searchFormParams.orderDirection = undefined; | ||||
|     // 清空时间查询  TODO  这块有点繁琐  有可以优化的地方吗?
 | ||||
|     // Form组件的resetFields方法无法清除datepicker里面的数据。
 | ||||
|     timeRange.value = []; | ||||
|     searchFormParams.beginTime = undefined; | ||||
|     searchFormParams.endTime = undefined; | ||||
|     tableRef.getTableRef().clearSort(); | ||||
| @ -138,15 +139,14 @@ export function useLoginLogHook() { | ||||
|     onSearch(); | ||||
|   } | ||||
| 
 | ||||
|   async function getLoginLogList(sort: Sort = defaultSort) { | ||||
|   async function getPostList(sort: Sort = defaultSort) { | ||||
|     pageLoading.value = true; | ||||
|     if (sort != null) { | ||||
|       CommonUtils.fillSortParams(searchFormParams, sort); | ||||
|     } | ||||
|     CommonUtils.fillPaginationParams(searchFormParams, pagination); | ||||
|     CommonUtils.fillTimeRangeParams(searchFormParams, timeRange.value); | ||||
| 
 | ||||
|     const { data } = await getLoginLogListApi(toRaw(searchFormParams)).finally( | ||||
|     const { data } = await getPostListApi(toRaw(searchFormParams)).finally( | ||||
|       () => { | ||||
|         pageLoading.value = false; | ||||
|       } | ||||
| @ -155,23 +155,23 @@ export function useLoginLogHook() { | ||||
|     pagination.total = data.total; | ||||
|   } | ||||
| 
 | ||||
|   async function exportAllExcel(sort: Sort = defaultSort) { | ||||
|     if (sort != null) { | ||||
|       CommonUtils.fillSortParams(searchFormParams, sort); | ||||
|   async function exportAllExcel() { | ||||
|     if (sortState.value != null) { | ||||
|       CommonUtils.fillSortParams(searchFormParams, sortState.value); | ||||
|     } | ||||
|     CommonUtils.fillPaginationParams(searchFormParams, pagination); | ||||
|     CommonUtils.fillTimeRangeParams(searchFormParams, timeRange.value); | ||||
| 
 | ||||
|     exportLoginLogExcelApi(toRaw(searchFormParams), "登录日志.xls"); | ||||
|     exportPostExcelApi(toRaw(searchFormParams), "岗位数据.xls"); | ||||
|   } | ||||
| 
 | ||||
|   async function handleDelete(row) { | ||||
|     await deleteLoginLogApi([row.logId]).then(() => { | ||||
|     await deletePostApi([row.logId]).then(() => { | ||||
|       message(`您删除了操作编号为${row.logId}的这条数据`, { | ||||
|         type: "success" | ||||
|       }); | ||||
|       // 刷新列表
 | ||||
|       getLoginLogList(); | ||||
|       getPostList(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| @ -193,12 +193,12 @@ export function useLoginLogHook() { | ||||
|       } | ||||
|     ) | ||||
|       .then(async () => { | ||||
|         await deleteLoginLogApi(multipleSelection.value).then(() => { | ||||
|         await deletePostApi(multipleSelection.value).then(() => { | ||||
|           message(`您删除了日志编号为[ ${multipleSelection.value} ]的数据`, { | ||||
|             type: "success" | ||||
|           }); | ||||
|           // 刷新列表
 | ||||
|           getLoginLogList(); | ||||
|           getPostList(); | ||||
|         }); | ||||
|       }) | ||||
|       .catch(() => { | ||||
| @ -210,9 +210,7 @@ export function useLoginLogHook() { | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   onMounted(() => { | ||||
|     getLoginLogList(); | ||||
|   }); | ||||
|   onMounted(getPostList); | ||||
| 
 | ||||
|   return { | ||||
|     searchFormParams, | ||||
| @ -224,9 +222,10 @@ export function useLoginLogHook() { | ||||
|     timeRange, | ||||
|     multipleSelection, | ||||
|     onSearch, | ||||
|     onSortChanged, | ||||
|     exportAllExcel, | ||||
|     // exportExcel,
 | ||||
|     getLoginLogList, | ||||
|     getPostList, | ||||
|     resetForm, | ||||
|     handleDelete, | ||||
|     handleBulkDelete | ||||
|  | ||||
| @ -1,55 +0,0 @@ | ||||
| <script setup lang="ts"> | ||||
| import { ref } from "vue"; | ||||
| import { formRules } from "./utils/rule"; | ||||
| import { FormProps } from "./utils/types"; | ||||
| 
 | ||||
| const props = withDefaults(defineProps<FormProps>(), { | ||||
|   formInline: () => ({ | ||||
|     name: "", | ||||
|     code: "", | ||||
|     remark: "" | ||||
|   }) | ||||
| }); | ||||
| 
 | ||||
| const ruleFormRef = ref(); | ||||
| const newFormInline = ref(props.formInline); | ||||
| 
 | ||||
| function getRef() { | ||||
|   return ruleFormRef.value; | ||||
| } | ||||
| 
 | ||||
| defineExpose({ getRef }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <el-form | ||||
|     ref="ruleFormRef" | ||||
|     :model="newFormInline" | ||||
|     :rules="formRules" | ||||
|     label-width="82px" | ||||
|   > | ||||
|     <el-form-item label="角色名称" prop="name"> | ||||
|       <el-input | ||||
|         v-model="newFormInline.name" | ||||
|         clearable | ||||
|         placeholder="请输入角色名称" | ||||
|       /> | ||||
|     </el-form-item> | ||||
| 
 | ||||
|     <el-form-item label="角色标识" prop="code"> | ||||
|       <el-input | ||||
|         v-model="newFormInline.code" | ||||
|         clearable | ||||
|         placeholder="请输入角色标识" | ||||
|       /> | ||||
|     </el-form-item> | ||||
| 
 | ||||
|     <el-form-item label="备注"> | ||||
|       <el-input | ||||
|         v-model="newFormInline.remark" | ||||
|         placeholder="请输入备注信息" | ||||
|         type="textarea" | ||||
|       /> | ||||
|     </el-form-item> | ||||
|   </el-form> | ||||
| </template> | ||||
| @ -4,17 +4,17 @@ import { useRole } from "./utils/hook"; | ||||
| import { PureTableBar } from "@/components/RePureTableBar"; | ||||
| import { useRenderIcon } from "@/components/ReIcon/src/hooks"; | ||||
| 
 | ||||
| // import Database from "@iconify-icons/ri/database-2-line"; | ||||
| // import More from "@iconify-icons/ep/more-filled"; | ||||
| import Delete from "@iconify-icons/ep/delete"; | ||||
| import EditPen from "@iconify-icons/ep/edit-pen"; | ||||
| import Search from "@iconify-icons/ep/search"; | ||||
| import Refresh from "@iconify-icons/ep/refresh"; | ||||
| import Menu from "@iconify-icons/ep/menu"; | ||||
| import AddFill from "@iconify-icons/ri/add-circle-line"; | ||||
| import { getRoleInfoApi, RoleDTO } from "@/api/system/role"; | ||||
| import RoleFormModal from "@/views/system/role/role-form-modal.vue"; | ||||
| import { ElMessage } from "element-plus"; | ||||
| 
 | ||||
| defineOptions({ | ||||
|   name: "Role" | ||||
|   name: "SystemRole" | ||||
| }); | ||||
| 
 | ||||
| const formRef = ref(); | ||||
| @ -24,19 +24,34 @@ const { | ||||
|   columns, | ||||
|   dataList, | ||||
|   pagination, | ||||
|   // buttonClass, | ||||
|   onSearch, | ||||
|   resetForm, | ||||
|   openDialog, | ||||
|   handleMenu, | ||||
|   handleDelete, | ||||
|   // handleDatabase, | ||||
|   handleSizeChange, | ||||
|   handleCurrentChange, | ||||
|   handleSelectionChange | ||||
|   menuTree, | ||||
|   getMenuTree, | ||||
|   handleDelete | ||||
| } = useRole(); | ||||
| </script> | ||||
| 
 | ||||
| const opType = ref<"add" | "update">("add"); | ||||
| const modalVisible = ref(false); | ||||
| const opRow = ref<RoleDTO>(); | ||||
| async function openDialog(type: "add" | "update", row?: RoleDTO) { | ||||
|   debugger; | ||||
|   try { | ||||
|     await getMenuTree(); | ||||
|     if (row) { | ||||
|       const { data } = await getRoleInfoApi(row.roleId); | ||||
|       row.selectedMenuList = data.selectedMenuList; | ||||
|       row.selectedDeptList = data.selectedDeptList; | ||||
|     } | ||||
|   } catch (e) { | ||||
|     console.error(e); | ||||
|     ElMessage.error((e as Error)?.message || "加载菜单失败"); | ||||
|   } | ||||
|   opType.value = type; | ||||
|   opRow.value = row; | ||||
|   modalVisible.value = true; | ||||
| } | ||||
| </script> | ||||
| <template> | ||||
|   <div class="main"> | ||||
|     <el-form | ||||
| @ -47,7 +62,7 @@ const { | ||||
|     > | ||||
|       <el-form-item label="角色名称:" prop="name"> | ||||
|         <el-input | ||||
|           v-model="form.name" | ||||
|           v-model="form.roleName" | ||||
|           placeholder="请输入角色名称" | ||||
|           clearable | ||||
|           class="!w-[200px]" | ||||
| @ -55,7 +70,7 @@ const { | ||||
|       </el-form-item> | ||||
|       <el-form-item label="角色标识:" prop="code"> | ||||
|         <el-input | ||||
|           v-model="form.code" | ||||
|           v-model="form.roleKey" | ||||
|           placeholder="请输入角色标识" | ||||
|           clearable | ||||
|           class="!w-[180px]" | ||||
| @ -96,7 +111,7 @@ const { | ||||
|         <el-button | ||||
|           type="primary" | ||||
|           :icon="useRenderIcon(AddFill)" | ||||
|           @click="openDialog()" | ||||
|           @click="openDialog('add')" | ||||
|         > | ||||
|           新增角色 | ||||
|         </el-button> | ||||
| @ -129,22 +144,12 @@ const { | ||||
|               type="primary" | ||||
|               :size="size" | ||||
|               :icon="useRenderIcon(EditPen)" | ||||
|               @click="openDialog('编辑', row)" | ||||
|               @click="openDialog('update', row)" | ||||
|             > | ||||
|               修改 | ||||
|             </el-button> | ||||
|             <el-button | ||||
|               class="reset-margin" | ||||
|               link | ||||
|               type="primary" | ||||
|               :size="size" | ||||
|               :icon="useRenderIcon(Menu)" | ||||
|               @click="handleMenu" | ||||
|             > | ||||
|               菜单权限 | ||||
|             </el-button> | ||||
|             <el-popconfirm | ||||
|               :title="`是否确认删除角色名称为${row.name}的这条数据`" | ||||
|               :title="`是否确认删除角色名称为${row.roleName}的这条数据`" | ||||
|               @confirm="handleDelete(row)" | ||||
|             > | ||||
|               <template #reference> | ||||
| @ -200,6 +205,13 @@ const { | ||||
|         </pure-table> | ||||
|       </template> | ||||
|     </PureTableBar> | ||||
| 
 | ||||
|     <role-form-modal | ||||
|       v-model="modalVisible" | ||||
|       :type="opType" | ||||
|       :row="opRow" | ||||
|       :menu-options="menuTree" | ||||
|     /> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										158
									
								
								src/views/system/role/role-form-modal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								src/views/system/role/role-form-modal.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,158 @@ | ||||
| <script setup lang="ts"> | ||||
| import VDialog from "@/components/VDialog/VDialog.vue"; | ||||
| import { computed, reactive, ref } from "vue"; | ||||
| import { useUserStoreHook } from "@/store/modules/user"; | ||||
| import { ElMessage, FormInstance, FormRules } from "element-plus"; | ||||
| import { | ||||
|   AddRoleCommand, | ||||
|   RoleDTO, | ||||
|   UpdateRoleCommand, | ||||
|   addRoleApi, | ||||
|   updateRoleApi | ||||
| } from "@/api/system/role"; | ||||
| import { MenuDTO } from "@/api/system/menu"; | ||||
| 
 | ||||
| interface Props { | ||||
|   type: "add" | "update"; | ||||
|   modelValue: boolean; | ||||
|   row?: RoleDTO; | ||||
|   menuOptions: MenuDTO[]; | ||||
| } | ||||
| 
 | ||||
| const props = defineProps<Props>(); | ||||
| const emits = defineEmits<{ | ||||
|   (e: "update:modelValue", v: boolean): void; | ||||
|   (e: "success"): void; | ||||
| }>(); | ||||
| 
 | ||||
| const visible = computed({ | ||||
|   get: () => props.modelValue, | ||||
|   set(v) { | ||||
|     emits("update:modelValue", v); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| const formData = reactive<AddRoleCommand | UpdateRoleCommand>({ | ||||
|   roleId: 0, | ||||
|   dataScope: "", | ||||
|   menuIds: [], | ||||
|   remark: "", | ||||
|   roleKey: "", | ||||
|   roleName: "", | ||||
|   roleSort: 1, | ||||
|   status: "" | ||||
| }); | ||||
| 
 | ||||
| const statusList = useUserStoreHook().dictionaryMap["common.status"]; | ||||
| 
 | ||||
| const rules: FormRules = { | ||||
|   roleName: [ | ||||
|     { | ||||
|       required: true, | ||||
|       message: "角色名称不能为空" | ||||
|     } | ||||
|   ], | ||||
|   roleKey: [ | ||||
|     { | ||||
|       required: true, | ||||
|       message: "权限标识不能为空" | ||||
|     } | ||||
|   ], | ||||
|   roleSort: [ | ||||
|     { | ||||
|       required: true, | ||||
|       message: "角色序号不能为空" | ||||
|     } | ||||
|   ] | ||||
| }; | ||||
| const formRef = ref<FormInstance>(); | ||||
| function handleOpened() { | ||||
|   console.log("opened", props.row); | ||||
|   if (props.row) { | ||||
|     Object.assign(formData, props.row); | ||||
|     formData.menuIds = props.row.selectedMenuList; | ||||
|   } else { | ||||
|     formRef.value?.resetFields(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const treeRef = ref<InstanceType<typeof ElTree>>(); | ||||
| function handleCheckChange() { | ||||
|   formData.menuIds = treeRef.value.getCheckedKeys(false) as number[]; | ||||
| } | ||||
| 
 | ||||
| const loading = ref(false); | ||||
| async function handleConfirm() { | ||||
|   try { | ||||
|     loading.value = true; | ||||
|     if (props.type === "add") { | ||||
|       await addRoleApi(formData); | ||||
|     } else if (props.type === "update") { | ||||
|       await updateRoleApi(formData as UpdateRoleCommand); | ||||
|     } | ||||
|     ElMessage.info("提交成功"); | ||||
|     visible.value = false; | ||||
|     emits("success"); | ||||
|   } catch (e) { | ||||
|     console.error(e); | ||||
|     ElMessage.error((e as Error)?.message || "提交失败"); | ||||
|   } finally { | ||||
|     loading.value = false; | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <v-dialog | ||||
|     show-full-screen | ||||
|     fixed-body-height | ||||
|     use-body-scrolling | ||||
|     :title="type === 'add' ? '新增角色' : '更新角色'" | ||||
|     v-model="visible" | ||||
|     :loading="loading" | ||||
|     @confirm="handleConfirm" | ||||
|     @cancel="visible = false" | ||||
|     @opened="handleOpened" | ||||
|   > | ||||
|     <el-form :model="formData" label-width="120px" :rules="rules" ref="formRef"> | ||||
|       <el-form-item prop="roleName" label="角色名称" required inline-message> | ||||
|         <el-input v-model="formData.roleName" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item prop="roleKey" label="权限字符" required> | ||||
|         <el-input v-model="formData.roleKey" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item prop="roleSort" label="角色顺序" required> | ||||
|         <el-input-number :min="1" v-model="formData.roleSort" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item prop="status" label="角色状态"> | ||||
|         <el-radio-group v-model="formData.status"> | ||||
|           <el-radio | ||||
|             v-for="item in Object.keys(statusList)" | ||||
|             :key="item" | ||||
|             :label="statusList[item].value" | ||||
|             >{{ statusList[item].label }}</el-radio | ||||
|           > | ||||
|         </el-radio-group> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="菜单权限" prop="menuIds"> | ||||
|         <el-tree | ||||
|           ref="treeRef" | ||||
|           :props="{ label: 'menuName', children: 'children' }" | ||||
|           :data="props.menuOptions" | ||||
|           node-key="id" | ||||
|           check-strictly | ||||
|           show-checkbox | ||||
|           default-expand-all | ||||
|           check-on-click-node | ||||
|           :expand-on-click-node="false" | ||||
|           :default-checked-keys="formData.menuIds" | ||||
|           @check-change="handleCheckChange" | ||||
|           style="width: 100%" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item prop="remark" label="备注" style="margin-bottom: 0"> | ||||
|         <el-input type="textarea" v-model="formData.remark" /> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|   </v-dialog> | ||||
| </template> | ||||
| @ -1,21 +1,24 @@ | ||||
| import dayjs from "dayjs"; | ||||
| import editForm from "../form.vue"; | ||||
| import { message } from "@/utils/message"; | ||||
| import { getRoleList } from "@/api/system"; | ||||
| import { ElMessageBox } from "element-plus"; | ||||
| import { | ||||
|   deleteRoleApi, | ||||
|   getRoleListApi, | ||||
|   RoleDTO, | ||||
|   RoleQuery | ||||
| } from "@/api/system/role"; | ||||
| import { getMenuListApi, MenuDTO } from "@/api/system/menu"; | ||||
| import { ElMessage, ElMessageBox } from "element-plus"; | ||||
| import { usePublicHooks } from "../../hooks"; | ||||
| import { addDialog } from "@/components/ReDialog"; | ||||
| import { type FormItemProps } from "../utils/types"; | ||||
| import { type PaginationProps } from "@pureadmin/table"; | ||||
| import { reactive, ref, onMounted, h, toRaw } from "vue"; | ||||
| import { reactive, ref, onMounted, toRaw } from "vue"; | ||||
| import { toTree } from "@/utils/tree"; | ||||
| 
 | ||||
| export function useRole() { | ||||
|   const form = reactive({ | ||||
|     name: "", | ||||
|     code: "", | ||||
|     status: "" | ||||
|   const form = reactive<RoleQuery>({ | ||||
|     roleKey: "", | ||||
|     roleName: "", | ||||
|     status: undefined | ||||
|   }); | ||||
|   const formRef = ref(); | ||||
|   const dataList = ref([]); | ||||
|   const loading = ref(true); | ||||
|   const switchLoadMap = ref({}); | ||||
| @ -29,17 +32,17 @@ export function useRole() { | ||||
|   const columns: TableColumnList = [ | ||||
|     { | ||||
|       label: "角色编号", | ||||
|       prop: "id", | ||||
|       prop: "roleId", | ||||
|       minWidth: 100 | ||||
|     }, | ||||
|     { | ||||
|       label: "角色名称", | ||||
|       prop: "name", | ||||
|       prop: "roleName", | ||||
|       minWidth: 120 | ||||
|     }, | ||||
|     { | ||||
|       label: "角色标识", | ||||
|       prop: "code", | ||||
|       prop: "roleKey", | ||||
|       minWidth: 150 | ||||
|     }, | ||||
|     { | ||||
| @ -131,34 +134,33 @@ export function useRole() { | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   function handleDelete(row) { | ||||
|     message(`您删除了角色名称为${row.name}的这条数据`, { type: "success" }); | ||||
|   async function handleDelete(row: RoleDTO) { | ||||
|     try { | ||||
|       loading.value = true; | ||||
|       await deleteRoleApi(row.roleId); | ||||
|       message(`您删除了角色名称为${row.roleName}的这条数据`, { type: "info" }); | ||||
|       onSearch(); | ||||
|     } catch (e) { | ||||
|       console.error(e); | ||||
|       message((e as Error)?.message || "删除失败", { type: "error" }); | ||||
|     } finally { | ||||
|       loading.value = false; | ||||
|     } | ||||
| 
 | ||||
|   function handleSizeChange(val: number) { | ||||
|     console.log(`${val} items per page`); | ||||
|   } | ||||
| 
 | ||||
|   function handleCurrentChange(val: number) { | ||||
|     console.log(`current page: ${val}`); | ||||
|   } | ||||
| 
 | ||||
|   function handleSelectionChange(val) { | ||||
|     console.log("handleSelectionChange", val); | ||||
|   } | ||||
| 
 | ||||
|   async function onSearch() { | ||||
|     try { | ||||
|       loading.value = true; | ||||
|     const { data } = await getRoleList(toRaw(form)); | ||||
|     dataList.value = data.list; | ||||
|       const { data } = await getRoleListApi(toRaw(form)); | ||||
|       console.log("role list", data); | ||||
|       dataList.value = data.rows; | ||||
|       pagination.total = data.total; | ||||
|     pagination.pageSize = data.pageSize; | ||||
|     pagination.currentPage = data.currentPage; | ||||
| 
 | ||||
|     setTimeout(() => { | ||||
|     } catch (e) { | ||||
|       console.error(e); | ||||
|       ElMessage.error((e as Error)?.message || "加载失败"); | ||||
|     } finally { | ||||
|       loading.value = false; | ||||
|     }, 500); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   const resetForm = formEl => { | ||||
| @ -167,59 +169,22 @@ export function useRole() { | ||||
|     onSearch(); | ||||
|   }; | ||||
| 
 | ||||
|   function openDialog(title = "新增", row?: FormItemProps) { | ||||
|     addDialog({ | ||||
|       title: `${title}角色`, | ||||
|       props: { | ||||
|         formInline: { | ||||
|           name: row?.name ?? "", | ||||
|           code: row?.code ?? "", | ||||
|           remark: row?.remark ?? "" | ||||
|         } | ||||
|       }, | ||||
|       width: "40%", | ||||
|       draggable: true, | ||||
|       fullscreenIcon: true, | ||||
|       closeOnClickModal: false, | ||||
|       contentRenderer: () => h(editForm, { ref: formRef }), | ||||
|       beforeSure: (done, { options }) => { | ||||
|         const FormRef = formRef.value.getRef(); | ||||
|         const curData = options.props.formInline as FormItemProps; | ||||
|         function chores() { | ||||
|           message(`您${title}了角色名称为${curData.name}的这条数据`, { | ||||
|             type: "success" | ||||
|           }); | ||||
|           done(); // 关闭弹框
 | ||||
|           onSearch(); // 刷新表格数据
 | ||||
|         } | ||||
|         FormRef.validate(valid => { | ||||
|           if (valid) { | ||||
|             console.log("curData", curData); | ||||
|             // 表单规则校验通过
 | ||||
|             if (title === "新增") { | ||||
|               // 实际开发先调用新增接口,再进行下面操作
 | ||||
|               chores(); | ||||
|             } else { | ||||
|               // 实际开发先调用编辑接口,再进行下面操作
 | ||||
|               chores(); | ||||
|             } | ||||
|           } | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   const menuTree = ref<MenuDTO[]>([]); | ||||
|   /** 菜单权限 */ | ||||
|   function handleMenu() { | ||||
|     message("等菜单管理页面开发后完善"); | ||||
|   async function getMenuTree() { | ||||
|     if (menuTree.value?.length) { | ||||
|       return menuTree.value; | ||||
|     } | ||||
|     const { data } = await getMenuListApi({ isButton: false }); | ||||
|     console.log("menu data", data); | ||||
|     menuTree.value = toTree(data, "id", "parentId"); | ||||
|     return menuTree.value; | ||||
|   } | ||||
| 
 | ||||
|   /** 数据权限 可自行开发 */ | ||||
|   // function handleDatabase() {}
 | ||||
| 
 | ||||
|   onMounted(() => { | ||||
|     onSearch(); | ||||
|   }); | ||||
|   onMounted(onSearch); | ||||
| 
 | ||||
|   return { | ||||
|     form, | ||||
| @ -227,15 +192,10 @@ export function useRole() { | ||||
|     columns, | ||||
|     dataList, | ||||
|     pagination, | ||||
|     // buttonClass,
 | ||||
|     onSearch, | ||||
|     resetForm, | ||||
|     openDialog, | ||||
|     handleMenu, | ||||
|     handleDelete, | ||||
|     // handleDatabase,
 | ||||
|     handleSizeChange, | ||||
|     handleCurrentChange, | ||||
|     handleSelectionChange | ||||
|     menuTree, | ||||
|     getMenuTree, | ||||
|     handleDelete | ||||
|   }; | ||||
| } | ||||
|  | ||||
| @ -1,8 +0,0 @@ | ||||
| import { reactive } from "vue"; | ||||
| import type { FormRules } from "element-plus"; | ||||
| 
 | ||||
| /** 自定义表单规则校验 */ | ||||
| export const formRules = reactive(<FormRules>{ | ||||
|   name: [{ required: true, message: "角色名称为必填项", trigger: "blur" }], | ||||
|   code: [{ required: true, message: "角色标识为必填项", trigger: "blur" }] | ||||
| }); | ||||
| @ -1,15 +0,0 @@ | ||||
| // 虽然字段很少 但是抽离出来 后续有扩展字段需求就很方便了
 | ||||
| 
 | ||||
| interface FormItemProps { | ||||
|   /** 角色名称 */ | ||||
|   name: string; | ||||
|   /** 角色编号 */ | ||||
|   code: string; | ||||
|   /** 备注 */ | ||||
|   remark: string; | ||||
| } | ||||
| interface FormProps { | ||||
|   formInline: FormItemProps; | ||||
| } | ||||
| 
 | ||||
| export type { FormItemProps, FormProps }; | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user