| <template> | 
|   <ProDialog | 
|     class="custom-dialog" | 
|     width="55%" | 
|     :title="dialogTitle" | 
|     v-model="visible" | 
|     :top="'10vh'" | 
|     :close-on-click-modal="false" | 
|     :close-on-press-escape="false" | 
|   > | 
|     <div class="authorize-wrapper"> | 
|       <div class="container-wrapper left-wrapper"> | 
|         <el-scrollbar> | 
|           <el-tree | 
|             class="companyTree" | 
|             :data="menusTree" | 
|             :props="{ | 
|               children: 'children', | 
|               label: 'name', | 
|             }" | 
|             node-key="id" | 
|             :expand-on-click-node="false" | 
|             :highlight-current="true" | 
|             default-expand-all | 
|             show-checkbox | 
|             ref="moduleTree" | 
|             :default-checked-keys="defaultCheckedKeys" | 
|             @check="handleModuleCheck" | 
|           > | 
|             <template #default="{ node }"> | 
|               <div class="custom-tree-node"> | 
|                 <type-tip :isMenu="node.data.type === EnumMenuType.Menu" /> | 
|                 <div class="node-text" @click="handleSelectModule(node.data)"> | 
|                   {{ node.label }} | 
|                 </div> | 
|               </div> | 
|             </template> | 
|           </el-tree> | 
|         </el-scrollbar> | 
|       </div> | 
|   | 
|       <div class="container-wrapper"> | 
|         <div class="type-wrapper"> | 
|           {{ SubModuleTitle[SubModuleType.PageButton] }} | 
|         </div> | 
|         <el-scrollbar> | 
|           <el-tree | 
|             v-show="!!state.currentMenuId" | 
|             :data="[ | 
|               { | 
|                 name: '全选', | 
|                 id: 'pageButtonAll', | 
|                 children: menuPageButtons, | 
|               }, | 
|             ]" | 
|             :props="{ | 
|               children: 'children', | 
|               label: 'name', | 
|             }" | 
|             node-key="id" | 
|             :expand-on-click-node="false" | 
|             :highlight-current="true" | 
|             default-expand-all | 
|             show-checkbox | 
|             ref="pageButtonTree" | 
|             :default-checked-keys="defaultCheckedKeys" | 
|             @check="handlePageButtonCheck" | 
|           > | 
|             <template #default="{ node }"> | 
|               <div class="custom-tree-node"> | 
|                 <span>{{ node.label }}</span> | 
|               </div> | 
|             </template> | 
|           </el-tree> | 
|         </el-scrollbar> | 
|       </div> | 
|   | 
|       <div class="container-wrapper"> | 
|         <div class="type-wrapper"> | 
|           {{ SubModuleTitle[SubModuleType.DataButton] }} | 
|         </div> | 
|         <el-scrollbar> | 
|           <el-tree | 
|             v-show="!!state.currentMenuId" | 
|             :data="[ | 
|               { | 
|                 name: '全选', | 
|                 id: 'dataButtonAll', | 
|                 children: menuDataButtons, | 
|               }, | 
|             ]" | 
|             :props="{ | 
|               children: 'children', | 
|               label: 'name', | 
|             }" | 
|             node-key="id" | 
|             :expand-on-click-node="false" | 
|             :highlight-current="true" | 
|             default-expand-all | 
|             show-checkbox | 
|             ref="dataButtonTree" | 
|             :default-checked-keys="defaultCheckedKeys" | 
|             @check="handleDataButtonCheck" | 
|           > | 
|             <template #default="{ node }"> | 
|               <div class="custom-tree-node"> | 
|                 <span>{{ node.label }}</span> | 
|               </div> | 
|             </template> | 
|           </el-tree> | 
|         </el-scrollbar> | 
|       </div> | 
|   | 
|       <div class="container-wrapper"> | 
|         <div class="type-wrapper"> | 
|           {{ SubModuleTitle[SubModuleType.Column] }} | 
|         </div> | 
|         <el-scrollbar> | 
|           <el-tree | 
|             v-show="!!state.currentMenuId" | 
|             :data="[ | 
|               { | 
|                 name: '全选', | 
|                 id: 'dataColumnAll', | 
|                 children: menuFields, | 
|               }, | 
|             ]" | 
|             :props="{ | 
|               children: 'children', | 
|               label: 'name', | 
|             }" | 
|             node-key="id" | 
|             :expand-on-click-node="false" | 
|             :highlight-current="true" | 
|             default-expand-all | 
|             show-checkbox | 
|             ref="dataColumnTree" | 
|             :default-checked-keys="defaultCheckedKeys" | 
|             @check="handleDataColumnCheck" | 
|           > | 
|             <template #default="{ node }"> | 
|               <div class="custom-tree-node"> | 
|                 <span>{{ node.label }}</span> | 
|               </div> | 
|             </template> | 
|           </el-tree> | 
|         </el-scrollbar> | 
|       </div> | 
|     </div> | 
|     <template #footer> | 
|       <span class="dialog-footer"> | 
|         <el-button @click="visible = false">取消</el-button> | 
|         <el-button type="primary" @click="handleConfirm" class="btn-submit"> 确定 </el-button> | 
|       </span> | 
|     </template> | 
|   </ProDialog> | 
| </template> | 
|   | 
| <script setup lang="ts"> | 
| import { Message } from '@bole-core/core'; | 
| import { AuthorizeType, SubModuleType, SubModuleTitle, EnumMenuType } from '@/constants'; | 
| import { getTree, treeEach } from '@/utils'; | 
| import { TreeInstance } from 'element-plus'; | 
| import { ProDialog } from '@bole-core/components'; | 
| import { useMenus, useMenu } from '@/hooks'; | 
| import data from '@iconify-icons/ep/tickets'; | 
|   | 
| const TypeTip = defineComponent({ | 
|   name: 'TypeTip', | 
|   props: ['isMenu'], | 
|   render() { | 
|     const { isMenu } = this; | 
|     const tipText = isMenu ? `菜单` : '页面'; | 
|     return h( | 
|       'span', | 
|       { | 
|         class: 'tip-text', | 
|         style: { | 
|           color: '#EB6100', | 
|         }, | 
|       }, | 
|       '[' + tipText + ']' | 
|     ); | 
|   }, | 
| }); | 
|   | 
| type Props = { | 
|   authorizeType: 'Role' | 'User'; | 
| }; | 
|   | 
| const props = withDefaults(defineProps<Props>(), {}); | 
|   | 
| const visible = defineModel({ type: Boolean }); | 
|   | 
| type Form = { | 
|   title?: string; | 
|   detail: API.GetRoleQueryResult; | 
| }; | 
|   | 
| const form = defineModel<Form>('form'); | 
|   | 
| const emit = defineEmits<{ | 
|   (e: 'onConfirm', selectedMenuIds: string[]): void; | 
|   (e: 'onCancel'): void; | 
| }>(); | 
|   | 
| type CheckedResourceItem = { | 
|   resourceId: string; | 
|   menuId: string; | 
|   resourceType: SubModuleType; | 
| }; | 
|   | 
| const state = reactive({ | 
|   currentMenuId: '', | 
|   selectedMenuIds: [] as string[], | 
| }); | 
|   | 
| const defaultCheckedKeys = computed(() => Array.from(state.selectedMenuIds)); | 
|   | 
| watch(visible, (newVal) => { | 
|   if (newVal) { | 
|     state.currentMenuId = ''; | 
|     const menuIds = form.value.detail?.menuIds ?? []; | 
|     // menuIds.forEach((id) => state.selectedMenuIds.add(id)); | 
|     state.selectedMenuIds = [...menuIds]; | 
|   } | 
| }); | 
|   | 
| const dialogTitle = computed(() => | 
|   props.authorizeType === 'Role' ? '角色功能授权' : '账号功能授权' | 
| ); | 
|   | 
| const moduleTree = useTemplateRef<TreeInstance>('moduleTree'); | 
| const pageButtonTree = useTemplateRef<TreeInstance>('pageButtonTree'); | 
| const dataButtonTree = useTemplateRef<TreeInstance>('dataButtonTree'); | 
| const dataColumnTree = useTemplateRef<TreeInstance>('dataColumnTree'); | 
|   | 
| type TreeRef = ReturnType<typeof useTemplateRef<TreeInstance>>; | 
|   | 
| const { menusTree, getMenuById } = useMenus({ | 
|   params: computed(() => ({ | 
|     userType: form.value.detail?.userType, | 
|     clientType: form.value.detail?.clientType, | 
|     roleId: form.value.detail?.id, | 
|   })), | 
|   enabled: computed(() => !!form.value.detail?.id), | 
| }); | 
|   | 
| const { menuFields, menuPageButtons, menuDataButtons } = useMenu({ | 
|   params: computed(() => ({ | 
|     id: state.currentMenuId, | 
|     roleId: form.value.detail?.id, | 
|   })), | 
|   enabled: computed(() => !!state.currentMenuId), | 
| }); | 
|   | 
| function handleSelectModule(menu: API.GetMenusQueryResultItem) { | 
|   state.currentMenuId = menu.id; | 
| } | 
|   | 
| function handleModuleCheck(data, params) { | 
|   handleCheck(data, params, moduleTree); | 
| } | 
|   | 
| function handlePageButtonCheck(data, params) { | 
|   handleCheck(data, params, pageButtonTree); | 
| } | 
|   | 
| function handleDataButtonCheck(data, params) { | 
|   handleCheck(data, params, dataButtonTree); | 
| } | 
|   | 
| function handleDataColumnCheck(data, params) { | 
|   handleCheck(data, params, dataColumnTree); | 
| } | 
|   | 
| function handleCheck(data, params, treeRef: TreeRef) { | 
|   const dataMap = {}; | 
|   treeEach( | 
|     [treeRef.value.store.root], | 
|     (node) => (dataMap[node.key] = node), | 
|     (node) => node.childNodes | 
|   ); | 
|   | 
|   const uncachedCheckedKeys = params.checkedKeys.filter( | 
|     (item) => !['pageButtonAll', 'dataButtonAll', 'dataColumnAll'].includes(item) | 
|   ); | 
|   const cachedKeys = state.selectedMenuIds.filter( | 
|     (item) => !(item in dataMap) && !uncachedCheckedKeys.includes(item) | 
|   ); | 
|   state.selectedMenuIds = cachedKeys.concat(uncachedCheckedKeys); | 
| } | 
|   | 
| function handleConfirm() { | 
|   emit('onConfirm', state.selectedMenuIds); | 
| } | 
| </script> | 
|   | 
| <style lang="scss" scoped> | 
| :deep(.custom-dialog) { | 
|   min-width: 900px; | 
| } | 
|   | 
| .authorize-wrapper { | 
|   display: flex; | 
|   | 
|   height: 500px; | 
|   border-bottom: 2px solid #f5f5f5; | 
|   background: #dddddd; | 
|   | 
|   .container-wrapper { | 
|     width: calc(25%); | 
|     border-right: 2px solid #f5f5f5; | 
|     // margin-right: 7px; | 
|     background: #ffffff; | 
|   | 
|     .type-wrapper { | 
|       display: flex; | 
|       justify-content: flex-start; | 
|       align-items: center; | 
|       padding: 10px 16px; | 
|       border-bottom: 2px solid #f5f5f5; | 
|       color: #333333; | 
|     } | 
|   | 
|     &:last-of-type { | 
|       margin-right: 0px; | 
|     } | 
|   | 
|     &.left-wrapper { | 
|       margin-right: 7px; | 
|       padding-top: 10px; | 
|       width: calc(35% - 7px); | 
|   | 
|       :deep(.el-scrollbar) { | 
|         width: 100%; | 
|         height: 98%; | 
|       } | 
|     } | 
|   | 
|     :deep(.el-scrollbar) { | 
|       width: 100%; | 
|       height: calc(98% - 43px); | 
|   | 
|       .el-scrollbar__wrap { | 
|         overflow: auto; | 
|   | 
|         .custom-tree-node { | 
|           display: flex; | 
|         } | 
|       } | 
|     } | 
|   } | 
| } | 
| </style> |