index.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import { addRoleMenu, delRoleMenuByRoleID, updateRoleMenu } from '@/services/permission'
  2. import { isNullOrUnDef } from '@/utils/is'
  3. import { DeleteOutlined, FormOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
  4. import { ModalForm, ProFormText } from '@ant-design/pro-form'
  5. import type { ProFormInstance } from '@ant-design/pro-form'
  6. import type { TreeDataNode } from 'antd'
  7. import { Button, Input, Tree, message, Popconfirm } from 'antd'
  8. import { useRef, useState } from 'react'
  9. import { useRequest } from '@umijs/max'
  10. const { DirectoryTree } = Tree
  11. import './index.less'
  12. export enum RoleType {
  13. SYSTEM = 'system', // 超级管理员
  14. SYSTEM_NORMAL = 'systemNormal', // 普通用户
  15. NORMAL = 'normal' // 手动添加的角色
  16. }
  17. type RoleLeftMenuProps = {
  18. defaultActiveRole?: string
  19. onSelect: (...args) => void
  20. showDelIcon: boolean
  21. onReloadStaff: () => void
  22. }
  23. const RoleLeftMenu: React.FC<RoleLeftMenuProps> = ({
  24. defaultActiveRole,
  25. onSelect,
  26. menuRoles,
  27. showDelIcon,
  28. onReloadStaff
  29. }) => {
  30. const formRef = useRef<ProFormInstance>(null)
  31. const [activeID, setActiveID] = useState<Nullable<string>>(null)
  32. const { run: tryAddRole } = useRequest((params: API.CreateRoleParams) => addRoleMenu(params), {
  33. manual: true,
  34. onSuccess: () => {
  35. onReloadStaff()
  36. }
  37. })
  38. const { run: tryUpdateRole } = useRequest(
  39. (params: Partial<API.UpdateRoleParams>) => updateRoleMenu(params),
  40. {
  41. manual: true,
  42. onSuccess: () => {
  43. message.success('修改成功')
  44. onReloadStaff()
  45. }
  46. }
  47. )
  48. const { run: tryDelRole } = useRequest((ID: string) => delRoleMenuByRoleID({ ID }), {
  49. manual: true,
  50. onSuccess: () => {
  51. message.success('删除成功')
  52. onReloadStaff()
  53. }
  54. })
  55. const handleOnFocus = async (
  56. e: React.FocusEvent<HTMLInputElement> | React.KeyboardEvent<HTMLElement>,
  57. oldTitle: string,
  58. ID: string
  59. ) => {
  60. const val = e.currentTarget.value || e.currentTarget.nodeValue
  61. if (val !== oldTitle) {
  62. await tryUpdateRole({ ID, name: val })
  63. }
  64. setActiveID(null)
  65. }
  66. function renderTreeNode(tree) {
  67. return tree.map((item: TreeDataNode & { roleType: string }) => {
  68. // 系统管理员,系统用户不能删除、编辑
  69. const newItem = {
  70. ...item,
  71. title: [RoleType.SYSTEM, RoleType.SYSTEM_NORMAL].includes(item.roleType) ? (
  72. <span className="department-node py-1">{item.title}</span>
  73. ) : (
  74. <div className="department-node py-1">
  75. {item.key === activeID ? (
  76. <Input
  77. autoFocus
  78. defaultValue={item.title}
  79. bordered={false}
  80. size="small"
  81. style={{ width: '70%', backgroundColor: 'white' }}
  82. onBlur={e => handleOnFocus(e, item.title, item.key)}
  83. onPressEnter={e => handleOnFocus(e, item.title, item.key)}
  84. />
  85. ) : (
  86. <div className="title">{item.title}</div>
  87. )}
  88. <div className="extra">
  89. <FormOutlined className="pr-2" onClick={() => setActiveID(item.key)} />
  90. <Popconfirm
  91. disabled={!showDelIcon}
  92. title="确认删除吗?"
  93. onText="确认"
  94. cancelText="取消"
  95. onConfirm={() => tryDelRole(item.key)}
  96. icon={<QuestionCircleOutlined style={{ color: 'red' }} />}>
  97. <DeleteOutlined
  98. onClick={() => {
  99. !showDelIcon && message.warning('请先移除该角色下的所有用户')
  100. }}
  101. />
  102. </Popconfirm>
  103. </div>
  104. </div>
  105. )
  106. }
  107. if (newItem.children?.length) {
  108. newItem.children = renderTreeNode(newItem.children)
  109. }
  110. return newItem
  111. })
  112. }
  113. const virtualHeight = document.getElementById('role-list')?.clientHeight
  114. if (!menuRoles?.length || isNullOrUnDef(defaultActiveRole)) return null
  115. return (
  116. <div className="min-w-54 rounded-3xl" style={{ height: 'calc(100vh - 122px)', background: '#ffffff' }}>
  117. <div className="menu-title flex items-center justify-around">
  118. <span className="py-4 text-base text-opacity-85">角色列表</span>
  119. <ModalForm
  120. title="新建角色"
  121. width="30%"
  122. onVisibleChange={visible => !visible && formRef.current?.resetFields()}
  123. layout="horizontal"
  124. isKeyPressSubmit
  125. trigger={
  126. <Button size="small" type="primary" ghost>
  127. <PlusOutlined />
  128. 添加
  129. </Button>
  130. }
  131. onFinish={async values => {
  132. await tryAddRole(values)
  133. message.success('添加成功')
  134. return true
  135. }}>
  136. <ProFormText name="name" label="角色名称" rules={[{ required: true, message: '请输入角色名' }]} />
  137. </ModalForm>
  138. </div>
  139. <div
  140. id="role-list"
  141. className="p-4 bg-white rounded-b-20px"
  142. style={{ height: 'calc(100% - 1rem*2 - 20px)' }}>
  143. {menuRoles.length ? (
  144. <DirectoryTree
  145. itemHeight={32}
  146. height={virtualHeight - 32}
  147. defaultSelectedKeys={[menuRoles[0]?.key]}
  148. onSelect={(keys, node) => onSelect(keys[0], node.node.roleType)}
  149. showIcon={false}
  150. treeData={renderTreeNode(menuRoles)}
  151. />
  152. ) : null}
  153. </div>
  154. </div>
  155. )
  156. }
  157. export default RoleLeftMenu