tree.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. import { Input, Tree } from 'antd';
  2. import {
  3. DeleteOutlined,
  4. EditOutlined,
  5. LoadingOutlined,
  6. PlusCircleOutlined,
  7. SearchOutlined,
  8. } from '@ant-design/icons';
  9. import { useEffect, useRef, useState } from 'react';
  10. import { service } from '@/pages/system/Department';
  11. import { Ellipsis, Empty, PermissionButton } from '@/components';
  12. import { useIntl, useLocation } from 'umi';
  13. import { cloneDeep, debounce, omit } from 'lodash';
  14. import Save from '../save';
  15. import { ISchema } from '@formily/json-schema';
  16. import { DepartmentItem } from '@/pages/system/Department/typings';
  17. import { ArrayToTree, onlyMessage } from '@/utils/util';
  18. import classnames from 'classnames';
  19. import _ from 'lodash';
  20. interface TreeProps {
  21. onSelect: (id: string) => void;
  22. }
  23. export const getSortIndex = (data: DepartmentItem[], pId?: string): number => {
  24. let sortIndex = 0;
  25. if (data.length) {
  26. if (!pId) {
  27. return data.sort((a, b) => b.sortIndex - a.sortIndex)[0].sortIndex + 1;
  28. }
  29. data.some((department) => {
  30. if (department.id === pId && department.children) {
  31. const sortArray = department.children.sort((a, b) => b.sortIndex - a.sortIndex);
  32. sortIndex = sortArray[0].sortIndex + 1;
  33. return true;
  34. } else if (department.children) {
  35. sortIndex = getSortIndex(department.children, pId);
  36. return !!sortIndex;
  37. }
  38. return false;
  39. });
  40. }
  41. return sortIndex;
  42. };
  43. const TreeMap = new Map();
  44. export default (props: TreeProps) => {
  45. const intl = useIntl();
  46. const [treeData, setTreeData] = useState<undefined | any[]>(undefined);
  47. const [treeDataList, setTreeDataList] = useState<undefined | any[]>(undefined);
  48. const [loading, setLoading] = useState(false);
  49. const [keys, setKeys] = useState<any[]>([]);
  50. const [visible, setVisible] = useState(false);
  51. const [data, setData] = useState<any>();
  52. const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
  53. const [showToolIndex, setShowToolIndex] = useState('');
  54. const searchKey = useRef('');
  55. const location = useLocation();
  56. const { permission } = PermissionButton.usePermission('system/Department');
  57. const handleTreeMap = (_data: any[]) => {
  58. if (_data) {
  59. _data.map((item) => {
  60. TreeMap.set(item.id, omit(cloneDeep(item), ['children']));
  61. if (item.children) {
  62. handleTreeMap(item.children);
  63. }
  64. });
  65. }
  66. };
  67. const queryTreeData = async () => {
  68. setKeys([]);
  69. const terms: Record<string, any> = {};
  70. if (searchKey.current) {
  71. terms.terms = [{ column: 'name$LIKE', value: `%${searchKey.current}%` }];
  72. }
  73. setLoading(true);
  74. const resp = await service.queryOrgThree({
  75. paging: false,
  76. sorts: [{ name: 'sortIndex', order: 'asc' }],
  77. ...terms,
  78. });
  79. setLoading(false);
  80. if (resp.status === 200) {
  81. handleTreeMap(resp.result);
  82. setTreeData(resp.result);
  83. if (resp.result && resp.result.length) {
  84. setKeys([resp.result[0].id]);
  85. }
  86. }
  87. };
  88. const queryList = (list: any, id: string, flag?: boolean) => {
  89. if (list && Array.isArray(list) && list.length) {
  90. return list.map((item) => {
  91. if (item.id === id || flag) {
  92. item.disabled = true;
  93. }
  94. if (item.children && Array.isArray(item.children) && item.children.length) {
  95. item.children = queryList(item.children, id, item.id === id || flag);
  96. }
  97. return item;
  98. });
  99. } else {
  100. return [];
  101. }
  102. };
  103. const updateOrg = (id: string) => {
  104. const list = _.cloneDeep(treeData);
  105. setTreeDataList(queryList(list, id));
  106. };
  107. const deleteItem = async (id: string) => {
  108. const response: any = await service.remove(id);
  109. if (response.status === 200) {
  110. onlyMessage(
  111. intl.formatMessage({
  112. id: 'pages.data.option.success',
  113. defaultMessage: '操作成功!',
  114. }),
  115. );
  116. queryTreeData();
  117. }
  118. };
  119. const searchByTreeMap = (key: string) => {
  120. const searchTree: string[] = [];
  121. const treeArray = new Map();
  122. if (key) {
  123. TreeMap.forEach((item) => {
  124. if (item.name.includes(key)) {
  125. searchTree.push(item.parentId);
  126. treeArray.set(item.id, item);
  127. }
  128. });
  129. function dig(_data: any[]): any {
  130. const pIds: string[] = [];
  131. if (!_data.length) return;
  132. _data.forEach((item) => {
  133. if (TreeMap.has(item)) {
  134. const _item = TreeMap.get(item);
  135. pIds.push(_item.parentId);
  136. treeArray.set(item, _item);
  137. }
  138. });
  139. }
  140. dig(searchTree);
  141. const arr = ArrayToTree(cloneDeep([...treeArray.values()]));
  142. setTreeData(arr);
  143. } else {
  144. setTreeData(ArrayToTree(cloneDeep([...TreeMap.values()])));
  145. }
  146. };
  147. const onSearchChange = (e: any) => {
  148. searchKey.current = e.target.value;
  149. // queryTreeData();
  150. searchByTreeMap(e.target.value);
  151. };
  152. const schema: ISchema = {
  153. type: 'object',
  154. properties: {
  155. parentId: {
  156. type: 'string',
  157. title: '上级组织',
  158. 'x-decorator': 'FormItem',
  159. 'x-component': 'TreeSelect',
  160. 'x-component-props': {
  161. fieldNames: {
  162. label: 'name',
  163. value: 'id',
  164. },
  165. placeholder: '请选择上级组织',
  166. },
  167. enum: treeDataList,
  168. },
  169. name: {
  170. type: 'string',
  171. title: intl.formatMessage({
  172. id: 'pages.table.name',
  173. defaultMessage: '名称',
  174. }),
  175. required: true,
  176. 'x-decorator': 'FormItem',
  177. 'x-component': 'Input',
  178. 'x-component-props': {
  179. placeholder: '请输入名称',
  180. },
  181. 'x-validator': [
  182. {
  183. max: 64,
  184. message: '最多可输入64个字符',
  185. },
  186. {
  187. required: true,
  188. message: '请输入名称',
  189. },
  190. ],
  191. },
  192. sortIndex: {
  193. type: 'string',
  194. title: intl.formatMessage({
  195. id: 'pages.device.instanceDetail.detail.sort',
  196. defaultMessage: '排序',
  197. }),
  198. required: true,
  199. 'x-decorator': 'FormItem',
  200. 'x-component': 'NumberPicker',
  201. 'x-component-props': {
  202. placeholder: '请输入排序',
  203. },
  204. 'x-validator': [
  205. {
  206. required: true,
  207. message: '请输入排序',
  208. },
  209. {
  210. pattern: /^[0-9]*[1-9][0-9]*$/,
  211. message: '请输入大于0的整数',
  212. },
  213. ],
  214. },
  215. },
  216. };
  217. useEffect(() => {
  218. if ((location as any).query?.save === 'true') {
  219. setData({ sortIndex: treeData && treeData.length + 1 });
  220. setVisible(true);
  221. setTreeDataList(treeData);
  222. }
  223. }, [location, treeData]);
  224. useEffect(() => {
  225. queryTreeData();
  226. TreeMap.clear();
  227. }, []);
  228. useEffect(() => {
  229. if (keys.length) {
  230. props.onSelect(keys[0]);
  231. }
  232. }, [keys]);
  233. return (
  234. <div className={'left-tree-content border-left'}>
  235. {loading && (
  236. <div className={'left-tree-loading'}>
  237. <LoadingOutlined />
  238. </div>
  239. )}
  240. <Input
  241. placeholder={'请输入组织名称'}
  242. className={'left-tree-search'}
  243. suffix={<SearchOutlined />}
  244. onChange={debounce(onSearchChange, 500)}
  245. />
  246. <PermissionButton
  247. key={'add'}
  248. style={{ width: '100%', margin: '24px 0' }}
  249. type="primary"
  250. isPermission={permission.add}
  251. onClick={() => {
  252. const sortIndex =
  253. treeData && treeData.length ? treeData[treeData.length - 1].sortIndex + 1 : 1;
  254. setData({ sortIndex });
  255. setVisible(true);
  256. setTreeDataList(treeData);
  257. }}
  258. >
  259. 新增
  260. </PermissionButton>
  261. {/* <Button
  262. style={{ width: '100%', margin: '24px 0' }}
  263. type={'primary'}
  264. onClick={() => {
  265. setData({ sortIndex: treeData && treeData.length + 1 });
  266. setVisible(true);
  267. }}
  268. >
  269. 新增
  270. </Button> */}
  271. {treeData ? (
  272. <div className={'left-tree-body'}>
  273. <Tree
  274. fieldNames={{
  275. title: 'name',
  276. key: 'id',
  277. }}
  278. blockNode={true}
  279. treeData={treeData}
  280. selectedKeys={keys}
  281. onSelect={(_keys: any[]) => {
  282. if (_keys && _keys.length) {
  283. setKeys(_keys);
  284. }
  285. }}
  286. expandedKeys={expandedKeys}
  287. onExpand={(_keys: any[]) => {
  288. setExpandedKeys(_keys);
  289. }}
  290. titleRender={(nodeData: any) => {
  291. return (
  292. <div
  293. className={classnames('tree-node-name')}
  294. onMouseEnter={() => {
  295. setShowToolIndex(nodeData.id);
  296. }}
  297. onMouseLeave={() => {
  298. setShowToolIndex('');
  299. }}
  300. >
  301. <span className={'tree-node-name--title'}>
  302. <Ellipsis title={nodeData.name} />
  303. </span>
  304. <span
  305. className={classnames('tree-node-name--btn', {
  306. 'show-btn': nodeData.id === showToolIndex,
  307. })}
  308. >
  309. <PermissionButton
  310. key="editable"
  311. tooltip={{
  312. title: intl.formatMessage({
  313. id: 'pages.data.option.edit',
  314. defaultMessage: '编辑',
  315. }),
  316. }}
  317. isPermission={permission.update}
  318. style={{ padding: '0 0 0 10px', height: 24 }}
  319. type="link"
  320. onClick={(e) => {
  321. e.stopPropagation();
  322. updateOrg(nodeData.id);
  323. setData({
  324. ...nodeData,
  325. });
  326. setVisible(true);
  327. }}
  328. >
  329. <EditOutlined />
  330. </PermissionButton>
  331. <PermissionButton
  332. key={'addChildren'}
  333. style={{ padding: '0 0 0 10px', height: 24 }}
  334. tooltip={{
  335. title: intl.formatMessage({
  336. id: 'pages.system.department.option.add',
  337. defaultMessage: '新增子组织',
  338. }),
  339. }}
  340. type="link"
  341. isPermission={permission.add}
  342. onClick={(e) => {
  343. e.stopPropagation();
  344. setData({
  345. parentId: nodeData.id,
  346. sortIndex: nodeData.children ? nodeData.children.length + 1 : 1,
  347. });
  348. setVisible(true);
  349. setTreeDataList(treeData);
  350. }}
  351. >
  352. <PlusCircleOutlined />
  353. </PermissionButton>
  354. <PermissionButton
  355. type="link"
  356. key="delete"
  357. style={{ padding: '0 0 0 10px', height: 24 }}
  358. popConfirm={{
  359. title: intl.formatMessage({
  360. id: 'pages.system.role.option.delete',
  361. defaultMessage: '确定要删除吗',
  362. }),
  363. onConfirm(e) {
  364. e?.stopPropagation();
  365. deleteItem(nodeData.id);
  366. },
  367. }}
  368. onClick={(e) => {
  369. e.stopPropagation();
  370. }}
  371. tooltip={{
  372. title: intl.formatMessage({
  373. id: 'pages.data.option.delete',
  374. defaultMessage: '删除',
  375. }),
  376. }}
  377. isPermission={permission.delete}
  378. >
  379. <DeleteOutlined />
  380. </PermissionButton>
  381. </span>
  382. </div>
  383. );
  384. }}
  385. />
  386. </div>
  387. ) : (
  388. <div style={{ height: 200 }}>
  389. <Empty />
  390. </div>
  391. )}
  392. <Save
  393. visible={visible}
  394. title={
  395. data && data.parentId
  396. ? intl.formatMessage({
  397. id: 'pages.system.department.option.add',
  398. })
  399. : undefined
  400. }
  401. service={service}
  402. onCancel={() => {
  403. setVisible(false);
  404. setData(undefined);
  405. }}
  406. reload={async (pId) => {
  407. await queryTreeData();
  408. if (pId && !expandedKeys.includes(pId)) {
  409. setExpandedKeys([...expandedKeys, pId]);
  410. }
  411. }}
  412. data={data}
  413. schema={schema}
  414. />
  415. </div>
  416. );
  417. };