permission.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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.map((item: any) => {
  97. return {
  98. label: item.name,
  99. value: item.action,
  100. };
  101. })}
  102. />
  103. </div>
  104. </div>
  105. );
  106. };
  107. export default (props: PermissionType) => {
  108. const [indeterminate, setIndeterminate] = useState(false);
  109. const [checkAll, setCheckAll] = useState(false);
  110. const [nodes, setNodes] = useState<React.ReactNode>([]);
  111. const checkListRef = useRef<CheckItem[]>([]);
  112. const intl = useIntl();
  113. const onChange = (list: CheckItem[]) => {
  114. if (props.onChange) {
  115. const _list = list
  116. .filter((a) => a.checked || a.actions.filter((b) => b.checked).length)
  117. .map((item) => ({
  118. permission: item.id,
  119. actions: item.actions.filter((b) => b.checked).map((b) => b.action),
  120. }));
  121. props.onChange(_list);
  122. }
  123. };
  124. /**
  125. * 全选或者全部取消
  126. * @param e
  127. */
  128. const onChangeAll = (e: CheckboxChangeEvent) => {
  129. const _list = props.data.map((item) => {
  130. return {
  131. ...item,
  132. actions: item.actions.map((a) => ({ ...a, checked: e.target.checked })),
  133. state: false,
  134. checked: e.target.checked,
  135. };
  136. });
  137. setIndeterminate(false);
  138. setCheckAll(e.target.checked);
  139. // setCheckedList(_list)
  140. checkListRef.current = _list;
  141. onChange(_list);
  142. setNodes(createContentNode(_list));
  143. };
  144. const parentChange = (value: ParentNodeChange) => {
  145. let indeterminateCount = 0;
  146. let _checkAll = 0;
  147. const list = checkListRef.current.map((item) => {
  148. const _checked = item.id === value.id ? value.checkedAll : item.checked;
  149. const _state = item.id === value.id ? value.state : item.state;
  150. const actions =
  151. item.id === value.id
  152. ? item.actions.map((a) => ({ ...a, checked: value.list.includes(a.action) }))
  153. : item.actions;
  154. if (_checked) {
  155. // 父checkbox为全选或者有子节点被选中
  156. _checkAll += 1;
  157. indeterminateCount += 1;
  158. } else if (_state) {
  159. // 父checkbox下
  160. indeterminateCount += 1;
  161. }
  162. return {
  163. ...item,
  164. actions,
  165. state: _state,
  166. checked: _checked,
  167. };
  168. });
  169. // 如果全部选中,则取消半选状态
  170. const isIndeterminate =
  171. _checkAll === list.length && _checkAll !== 0 ? false : !!indeterminateCount;
  172. setIndeterminate(isIndeterminate);
  173. setCheckAll(_checkAll === list.length && _checkAll !== 0);
  174. // setCheckedList(list)
  175. checkListRef.current = list;
  176. onChange(list);
  177. };
  178. /**
  179. * 创建节点
  180. */
  181. function createContentNode(data: CheckItem[]): React.ReactNode[] {
  182. const NodeArr: React.ReactNode[] = [];
  183. if (data && data.length) {
  184. data.forEach((item) => {
  185. if (item.actions) {
  186. // 父节点
  187. NodeArr.push(
  188. <ParentNode
  189. {...item}
  190. key={item.id}
  191. disabled={props.disabled}
  192. onChange={parentChange}
  193. />,
  194. );
  195. }
  196. });
  197. }
  198. return NodeArr;
  199. }
  200. /**
  201. * 初始化树形节点数据格式
  202. * @param data
  203. */
  204. const initialState = (data: PermissionDataType[]) => {
  205. const _list = data.map((item) => {
  206. const propsPermission =
  207. props.value && props.value.length
  208. ? props.value.find((p) => p.permission === item.id)
  209. : undefined;
  210. const propsActions = propsPermission ? propsPermission.actions : [];
  211. return {
  212. ...item,
  213. actions: item.actions.map((a) => ({ ...a, checked: propsActions.includes(a.action) })),
  214. state: false, // 是否为半选中状态
  215. checked: false, // 是否为全选
  216. };
  217. });
  218. // setCheckedList(_list)
  219. checkListRef.current = _list;
  220. setNodes(createContentNode(_list));
  221. };
  222. useEffect(() => {
  223. if (props.data) {
  224. initialState(props.data);
  225. }
  226. /* eslint-disable */
  227. }, [props.data, props.disabled]);
  228. return (
  229. <div className="permission-container">
  230. <div className="permission-header">{props.title}</div>
  231. <div className="permission-content">
  232. <div className="permission-items">
  233. <div className="permission-parent">
  234. <Checkbox
  235. onChange={onChangeAll}
  236. indeterminate={indeterminate}
  237. checked={checkAll}
  238. disabled={props.disabled}
  239. >
  240. {intl.formatMessage({
  241. id: 'pages.system.menu.root',
  242. defaultMessage: '菜单权限',
  243. })}
  244. </Checkbox>
  245. </div>
  246. </div>
  247. {nodes}
  248. </div>
  249. </div>
  250. );
  251. };