index.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import { DeleteOutlined, FormOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
  2. import { ModalForm, ProFormText } from '@ant-design/pro-form'
  3. import { useRequest } from '@umijs/max'
  4. import type { ProFormInstance } from '@ant-design/pro-form'
  5. import { Button, Input, message, Popconfirm, Tree } from 'antd'
  6. import type { DirectoryTreeProps } from 'antd/lib/tree'
  7. import { addDataSource, delDataSourceID, updateDataSourceItem } from '@/services/api/schema'
  8. import { useRef, useState } from 'react'
  9. import '@/pages/Permission/FrontRole/components/RoleLeftMenu/index.less'
  10. const { DirectoryTree } = Tree
  11. type LeftMenuProps = {
  12. onSelect: (key: string) => void
  13. defaultActiveID: string
  14. options: API.DataSourceMenuItem[]
  15. initFn: () => Promise<void>
  16. }
  17. const LeftMenu: React.FC<LeftMenuProps> = ({ onSelect, defaultActiveID, options, showDelIcon, initFn }) => {
  18. const [activeID, setActiveID] = useState<Nullable<string>>(null)
  19. const formRef = useRef<ProFormInstance>(null)
  20. const handleOnSelect: DirectoryTreeProps['onSelect'] = (keys, node) => {
  21. // console.log('Trigger Select', node)
  22. onSelect?.(keys[0], node)
  23. }
  24. const { run: tryUpdateDataSourceItem } = useRequest(
  25. (params: Partial<API.UpdateRoleParams>) => updateDataSourceItem(params),
  26. {
  27. manual: true,
  28. onSuccess: () => {
  29. message.success('修改成功')
  30. initFn()
  31. }
  32. }
  33. )
  34. const { run: tryAddDataSource } = useRequest((params: API.CreateRoleParams) => addDataSource(params), {
  35. manual: true,
  36. onSuccess: () => {
  37. initFn()
  38. }
  39. })
  40. const { run: tryDelRole } = useRequest((ID: string) => delDataSourceID({ ID }), {
  41. manual: true,
  42. onSuccess: () => {
  43. message.success('删除成功')
  44. initFn()
  45. }
  46. })
  47. const handleOnFocus = async (
  48. e: React.FocusEvent<HTMLInputElement> | React.KeyboardEvent<HTMLElement>,
  49. oldTitle: string,
  50. ID: string
  51. ) => {
  52. const val = e.currentTarget.value || e.currentTarget.nodeValue
  53. if (val !== oldTitle) {
  54. await tryUpdateDataSourceItem({ ID, name: val })
  55. }
  56. setActiveID(null)
  57. }
  58. const renderTreeNode = tree => {
  59. return tree.map((item: API.DataSourceMenuItem & { title: string; key: string }) => {
  60. const newItem = {
  61. ...item,
  62. title: (
  63. <div className="department-node py-1">
  64. {item.ID === activeID ? (
  65. <Input
  66. autoFocus
  67. defaultValue={item.name}
  68. bordered={false}
  69. size="small"
  70. style={{ width: '70%', backgroundColor: 'white' }}
  71. onBlur={e => handleOnFocus(e, item.name, item.ID)}
  72. onPressEnter={e => handleOnFocus(e, item.name, item.ID)}
  73. />
  74. ) : (
  75. <div className="title">{item.name}</div>
  76. )}
  77. <div className="extra">
  78. <FormOutlined className="pr-2" onClick={() => setActiveID(item.ID)} />
  79. {item.ID === defaultActiveID ? (
  80. <Popconfirm
  81. disabled={!showDelIcon}
  82. title="确认删除吗?"
  83. onText="确认"
  84. cancelText="取消"
  85. onConfirm={() => tryDelRole(item.ID)}
  86. icon={<QuestionCircleOutlined style={{ color: 'red' }} />}>
  87. <DeleteOutlined
  88. onClick={() => {
  89. !showDelIcon && message.warning('该数据源已绑定子项,不允许删除')
  90. }}
  91. />
  92. </Popconfirm>
  93. ) : null}
  94. </div>
  95. </div>
  96. )
  97. }
  98. if (newItem.children?.length) {
  99. newItem.children = renderTreeNode(newItem.items)
  100. }
  101. return newItem
  102. })
  103. }
  104. const virtualHeigh = document.getElementById('role-list')?.clientHeight
  105. return (
  106. <div
  107. className="min-w-54 rounded-20px flex flex-col bg-white "
  108. style={{ height: 'calc(100vh - 122px)', background: '#ffffff' }}>
  109. <div className="menu-title flex items-center justify-around">
  110. <div className="py-4 text-base text-opacity-85">数据源列表</div>
  111. <ModalForm
  112. layout="horizontal"
  113. title="新增数据源"
  114. width="30%"
  115. formRef={formRef}
  116. onVisibleChange={visible => !visible && formRef.current?.resetFields()}
  117. isKeyPressSubmit
  118. labelCol={{ span: 5 }}
  119. trigger={
  120. <Button size="small" type="primary" ghost>
  121. <PlusOutlined />
  122. 添加
  123. </Button>
  124. }
  125. onFinish={async values => {
  126. await tryAddDataSource(values)
  127. message.success('添加成功')
  128. return true
  129. }}>
  130. <ProFormText
  131. label="数据源名称"
  132. name="name"
  133. rules={[{ required: true, message: '请输入数据源名称' }]}
  134. />
  135. {/* <ProFormText label="URL" name="url" required disabled placeholder="自动生成" /> */}
  136. </ModalForm>
  137. </div>
  138. <div
  139. id="role-list"
  140. className="p-4 bg-white rounded-b-20px"
  141. style={{ height: 'calc(100% - 1rem*2 - 20px)' }}>
  142. {options.length ? (
  143. <DirectoryTree
  144. treeData={renderTreeNode(options.map(item => ({ title: item.name, key: item.ID, ...item })))}
  145. height={virtualHeigh - 32}
  146. defaultSelectedKeys={[options[0]?.ID]}
  147. onSelect={handleOnSelect}
  148. showIcon={false}
  149. defaultExpandAll
  150. />
  151. ) : null}
  152. </div>
  153. </div>
  154. )
  155. }
  156. export default LeftMenu