permission.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import React, { useEffect, useState, useRef } from 'react';
  2. import { Checkbox } from 'antd';
  3. import './permission.less';
  4. import type { CheckboxChangeEvent } from 'antd/es/checkbox';
  5. import type { PermissionInfo } from '../typing';
  6. import { useIntl } from 'umi';
  7. type PermissionDataType = {
  8. action: string;
  9. id: string;
  10. name: string;
  11. checked?: boolean;
  12. actions: PermissionDataType[];
  13. };
  14. type PermissionType = {
  15. value?: {
  16. permission: string;
  17. actions: string[];
  18. }[];
  19. data: PermissionDataType[];
  20. title?: React.ReactNode | string;
  21. onChange?: (data: PermissionInfo[]) => void;
  22. disabled?: boolean;
  23. };
  24. type ParentNodeChange = { checkedAll: boolean; list: string[]; id: string; state: boolean };
  25. type ParentNodeType = {
  26. id: string;
  27. name: string;
  28. actions: PermissionDataType[];
  29. onChange?: (value: ParentNodeChange) => void;
  30. disabled?: boolean;
  31. checked?: boolean;
  32. state?: boolean;
  33. };
  34. type CheckItem = Omit<ParentNodeType, 'onChange'>;
  35. const ParentNode = (props: ParentNodeType) => {
  36. const { actions, checked } = props;
  37. const [checkedList, setCheckedList] = useState<string[]>([]);
  38. const [indeterminate, setIndeterminate] = useState(false);
  39. const [checkAll, setCheckAll] = useState(false);
  40. const submitData = (checkedAll: boolean, list: string[], state: boolean) => {
  41. if (props.onChange) {
  42. props.onChange({
  43. checkedAll,
  44. list,
  45. id: props.id,
  46. state,
  47. });
  48. }
  49. };
  50. const onChange = (list: any) => {
  51. const _indeterminate = !!list.length && list.length < props.actions.length;
  52. setCheckedList(list);
  53. setIndeterminate(_indeterminate);
  54. setCheckAll(list.length === props.actions.length);
  55. submitData(list.length === props.actions.length, list, _indeterminate);
  56. };
  57. const onChangeAll = (e: CheckboxChangeEvent) => {
  58. const _list = e.target.checked ? props.actions.map((item) => item.action) : [];
  59. setCheckedList(e.target.checked ? _list : []);
  60. setIndeterminate(false);
  61. setCheckAll(e.target.checked);
  62. submitData(e.target.checked, _list, false);
  63. };
  64. useEffect(() => {
  65. onChangeAll({
  66. target: {
  67. checked: !!props.checked,
  68. },
  69. } as CheckboxChangeEvent);
  70. /* eslint-disable */
  71. }, [checked]);
  72. useEffect(() => {
  73. // 通过父级传入checked来控制节点状态
  74. const _list = props.actions.filter((a) => a.checked).map((a) => a.action);
  75. onChange(_list);
  76. /* eslint-disable */
  77. }, [actions]);
  78. return (
  79. <div className="permission-items">
  80. <div className="permission-parent">
  81. <Checkbox
  82. id={props.id}
  83. onChange={onChangeAll}
  84. indeterminate={indeterminate}
  85. checked={checkAll}
  86. disabled={props.disabled}
  87. >
  88. {props.name}
  89. </Checkbox>
  90. </div>
  91. <div className="permission-children-checkbox">
  92. <Checkbox.Group
  93. onChange={onChange}
  94. value={checkedList}
  95. disabled={props.disabled}
  96. options={props.actions
  97. .filter((a) => a.action)
  98. .map((item) => {
  99. return {
  100. label: item.name,
  101. value: item.action,
  102. };
  103. })}
  104. />
  105. </div>
  106. </div>
  107. );
  108. };
  109. export default (props: PermissionType) => {
  110. const [indeterminate, setIndeterminate] = useState(false);
  111. const [checkAll, setCheckAll] = useState(false);
  112. const [nodes, setNodes] = useState<React.ReactNode>([]);
  113. const checkListRef = useRef<CheckItem[]>([]);
  114. const intl = useIntl();
  115. const onChange = (list: CheckItem[]) => {
  116. if (props.onChange) {
  117. const _list = list
  118. .filter((a) => a.checked || a.actions.filter((b) => b.checked).length)
  119. .map((item) => ({
  120. permission: item.id,
  121. actions: item.actions.filter((b) => b.checked).map((b) => b.action),
  122. }));
  123. props.onChange(_list);
  124. }
  125. };
  126. /**
  127. * 全选或者全部取消
  128. * @param e
  129. */
  130. const onChangeAll = (e: CheckboxChangeEvent) => {
  131. const _list = props.data.map((item) => {
  132. return {
  133. ...item,
  134. actions: item.actions.map((a) => ({ ...a, checked: e.target.checked })),
  135. state: false,
  136. checked: e.target.checked,
  137. };
  138. });
  139. setIndeterminate(false);
  140. setCheckAll(e.target.checked);
  141. // setCheckedList(_list)
  142. checkListRef.current = _list;
  143. onChange(_list);
  144. setNodes(createContentNode(_list));
  145. };
  146. const parentChange = (value: ParentNodeChange) => {
  147. let indeterminateCount = 0;
  148. let _checkAll = 0;
  149. const list = checkListRef.current.map((item) => {
  150. const _checked = item.id === value.id ? value.checkedAll : item.checked;
  151. const _state = item.id === value.id ? value.state : item.state;
  152. const actions =
  153. item.id === value.id
  154. ? item.actions.map((a) => ({ ...a, checked: value.list.includes(a.action) }))
  155. : item.actions;
  156. if (_checked) {
  157. // 父checkbox为全选或者有子节点被选中
  158. _checkAll += 1;
  159. indeterminateCount += 1;
  160. } else if (_state) {
  161. // 父checkbox下
  162. indeterminateCount += 1;
  163. }
  164. return {
  165. ...item,
  166. actions,
  167. state: _state,
  168. checked: _checked,
  169. };
  170. });
  171. // 如果全部选中,则取消半选状态
  172. const isIndeterminate =
  173. _checkAll === list.length && _checkAll !== 0 ? false : !!indeterminateCount;
  174. setIndeterminate(isIndeterminate);
  175. setCheckAll(_checkAll === list.length && _checkAll !== 0);
  176. // setCheckedList(list)
  177. checkListRef.current = list;
  178. onChange(list);
  179. };
  180. /**
  181. * 创建节点
  182. */
  183. function createContentNode(data: CheckItem[]): React.ReactNode[] {
  184. const NodeArr: React.ReactNode[] = [];
  185. if (data && data.length) {
  186. data.forEach((item) => {
  187. if (item.actions) {
  188. // 父节点
  189. NodeArr.push(
  190. <ParentNode
  191. {...item}
  192. key={item.id}
  193. disabled={props.disabled}
  194. onChange={parentChange}
  195. />,
  196. );
  197. }
  198. });
  199. }
  200. return NodeArr;
  201. }
  202. /**
  203. * 初始化树形节点数据格式
  204. * @param data
  205. */
  206. const initialState = (data: PermissionDataType[]) => {
  207. const _list = data.map((item) => {
  208. const propsPermission =
  209. props.value && props.value.length
  210. ? props.value.find((p) => p.permission === item.id)
  211. : undefined;
  212. const propsActions = propsPermission ? propsPermission.actions : [];
  213. return {
  214. ...item,
  215. actions: item.actions.map((a) => ({ ...a, checked: propsActions.includes(a.action) })),
  216. state: false, // 是否为半选中状态
  217. checked: false, // 是否为全选
  218. };
  219. });
  220. // setCheckedList(_list)
  221. checkListRef.current = _list;
  222. setNodes(createContentNode(_list));
  223. };
  224. useEffect(() => {
  225. if (props.data) {
  226. initialState(props.data);
  227. }
  228. /* eslint-disable */
  229. }, [props.data, props.disabled]);
  230. return (
  231. <div className="permission-container">
  232. <div className="permission-header">{props.title}</div>
  233. <div className="permission-content">
  234. <div className="permission-items">
  235. <div className="permission-parent">
  236. <Checkbox
  237. onChange={onChangeAll}
  238. indeterminate={indeterminate}
  239. checked={checkAll}
  240. disabled={props.disabled}
  241. >
  242. {intl.formatMessage({
  243. id: 'pages.system.menu.root',
  244. defaultMessage: '菜单权限',
  245. })}
  246. </Checkbox>
  247. </div>
  248. </div>
  249. {nodes}
  250. </div>
  251. </div>
  252. );
  253. };