tree.tsx 10 KB

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