index.tsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import { Button, message } from 'antd'
  2. import LeftMenu from './Components/LeftMenu'
  3. import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc'
  4. import { arrayMoveImmutable } from 'array-move'
  5. import type { ColumnsType } from 'antd/lib/table'
  6. import type { SortableContainerProps, SortEnd } from 'react-sortable-hoc'
  7. import { useRef, useState } from 'react'
  8. import { useRequest } from 'umi'
  9. import type { ProFormInstance } from '@ant-design/pro-form'
  10. import { addDataSourceItem, queryDataSource, updateDataSourceItem } from '@/services/api/schema'
  11. import { MenuOutlined, PlusOutlined } from '@ant-design/icons'
  12. import classNames from 'classnames'
  13. import { ModalForm, ProFormRadio, ProFormText } from '@ant-design/pro-form'
  14. import ProTable from '@ant-design/pro-table'
  15. import { ModalType } from '@/utils/enum'
  16. enum OptionModalType {
  17. ADD,
  18. UPDATE
  19. }
  20. const DragHandle = SortableHandle(() => <MenuOutlined style={{ cursor: 'grab', color: '#999' }} />)
  21. const SortableItem = SortableElement((props: React.HTMLAttributes<HTMLTableRowElement>) => (
  22. <tr {...props} />
  23. ))
  24. const SortableBody = SortableContainer((props: React.HTMLAttributes<HTMLTableSectionElement>) => (
  25. <tbody {...props} />
  26. ))
  27. type iState = {
  28. menuData: API.DataSourceMenuItem[]
  29. activeID: Nullable<string>
  30. current: API.DataSourceItem[]
  31. modalType: ModalType
  32. menuDataItems: API.DataSourceItem[]
  33. items: API.DataSourceItem[]
  34. }
  35. const Option = () => {
  36. const formRef = useRef<ProFormInstance>(null)
  37. const [state, setState] = useState<iState>({
  38. menuData: [],
  39. activeID: null,
  40. modalVisible: false,
  41. modalType: OptionModalType.ADD,
  42. menuDataItems: [],
  43. items: [],
  44. current: {
  45. ID: null,
  46. name: null,
  47. enable: true
  48. }
  49. })
  50. console.log(state.menuDataItems)
  51. const { run: tryFetchList } = useRequest(queryDataSource, {
  52. onSuccess: (result: API.DataSourceMenuItem) => {
  53. setState({ ...state, menuData: result })
  54. }
  55. })
  56. const { run: tryAddDataSourceItem } = useRequest(
  57. (params: API.DataSourceItem) => addDataSourceItem(params),
  58. {
  59. manual: true,
  60. onSuccess: async () => {
  61. await tryFetchList()
  62. }
  63. }
  64. )
  65. const { run: tryUpdateDataSourceItem } = useRequest(
  66. (params: API.DataSourceItem) => updateDataSourceItem(params),
  67. {
  68. manual: true,
  69. onSuccess: async () => {
  70. await tryFetchList()
  71. }
  72. }
  73. )
  74. const onSelect = (key: string, node) => {
  75. setState({ ...state, activeID: key, menuDataItems: node.node.items })
  76. }
  77. const columns: ColumnsType<API.DataSourceItem> = [
  78. {
  79. title: '排序',
  80. dataIndex: 'sort',
  81. width: 50,
  82. render: () => <DragHandle />
  83. },
  84. {
  85. title: '序号',
  86. dataIndex: 'num',
  87. width: 50,
  88. render: (num, record, index) => `${index + 1}`
  89. },
  90. {
  91. title: '选项名称',
  92. dataIndex: 'name'
  93. },
  94. {
  95. title: '状态',
  96. dataIndex: 'enable',
  97. render: text => (
  98. <div className="flex items-center">
  99. <span
  100. className={classNames(
  101. 'w-3 h-3 rounded-1/2 inline-flex',
  102. text ? 'bg-green-500' : 'bg-red-500'
  103. )}
  104. />
  105. <span className="ml-1">{text ? '已启用' : '已停用'}</span>
  106. </div>
  107. )
  108. },
  109. {
  110. title: '操作',
  111. dataIndex: 'opreate',
  112. render: (_, record) => (
  113. <div
  114. className="text-primary cursor-pointer hover:text-hex-967bbd"
  115. onClick={() => {
  116. setTimeout(() => {
  117. formRef.current?.setFieldsValue({ ...record })
  118. }, 80)
  119. setState({
  120. ...state,
  121. modalType: ModalType.UPDATE,
  122. modalVisible: true,
  123. current: {
  124. ID: record.ID,
  125. name: record.name,
  126. enable: record.enable
  127. }
  128. })
  129. }}>
  130. 编辑
  131. </div>
  132. )
  133. }
  134. ]
  135. const onSortEnd = ({ oldIndex, newIndex }: SortEnd) => {
  136. if (oldIndex !== newIndex) {
  137. const newData = arrayMoveImmutable(dataSource.slice(), oldIndex, newIndex).filter(
  138. (el: DataType) => !!el
  139. )
  140. const newMenuData = menuData.map(item => {
  141. const newItem = { ...item }
  142. if (item.ID === state.activeID) {
  143. newItem.items = newData
  144. return newItem
  145. }
  146. return item
  147. })
  148. setState({ ...state, menuData: newMenuData })
  149. }
  150. }
  151. const DraggableContainer = (props: SortableContainerProps) => (
  152. <SortableBody
  153. useDragHandle
  154. disableAutoscroll
  155. helperClass="row-dragging"
  156. onSortEnd={onSortEnd}
  157. {...props}
  158. />
  159. )
  160. const DraggableBodyRow = ({ className, style, ...restProps }) => {
  161. const index = state.menuData
  162. .find(item => item.ID === state.activeID)
  163. ?.items.findIndex(x => x.index === restProps['data-row-key'])
  164. return <SortableItem index={index} {...restProps} />
  165. }
  166. return (
  167. <div className="flex flex-nowrap h-full">
  168. <LeftMenu
  169. onSelect={onSelect}
  170. showDelIcon={!state.menuDataItems.length}
  171. options={state.menuData}
  172. initFn={() => tryFetchList()}
  173. />
  174. <div className="ml-6 shadow-hex-3e2c5a" style={{ width: 'calc(100% - 12rem)' }}>
  175. <ProTable<API.DataSourceItem>
  176. columns={columns}
  177. search={false}
  178. dataSource={state.menuData.find(item => item.ID === state.activeID)?.items || []}
  179. rowKey="ID"
  180. toolbar={{
  181. actions: [
  182. <Button
  183. key="btn-key"
  184. size="small"
  185. type="primary"
  186. onClick={() => {
  187. setState({
  188. ...state,
  189. modalType: OptionModalType.ADD,
  190. modalVisible: true
  191. })
  192. }}
  193. ghost>
  194. <PlusOutlined />
  195. 添加选项
  196. </Button>
  197. ]
  198. }}
  199. components={{
  200. body: {
  201. wrapper: DraggableContainer,
  202. row: DraggableBodyRow
  203. }
  204. }}
  205. />
  206. <ModalForm
  207. labelCol={{ span: 6 }}
  208. key="form"
  209. width="30%"
  210. title={`${state.modalType === OptionModalType.ADD ? '添加' : '编辑'}选项`}
  211. formRef={formRef}
  212. layout="horizontal"
  213. visible={state.modalVisible}
  214. onVisibleChange={visible => {
  215. setState({ ...state, modalVisible: visible })
  216. setTimeout(() => {
  217. if (!visible) formRef.current?.resetFields()
  218. }, 80)
  219. }}
  220. initialValues={{ enable: true }}
  221. onFinish={async values => {
  222. try {
  223. if (state.modalType === OptionModalType.ADD) {
  224. await tryAddDataSourceItem({ ...values, dataSourceID: state.activeID })
  225. setState({
  226. ...state,
  227. menuDataItems: state.menuData.find(i => i.ID === state.activeID)?.items
  228. })
  229. } else {
  230. const newItemData = state.menuDataItems.map(item => {
  231. if (item.ID === state.current.ID) {
  232. const newItem = { ...values }
  233. return newItem
  234. }
  235. return item
  236. })
  237. setState({ ...state, items: newItemData })
  238. await tryUpdateDataSourceItem({
  239. ID: state.activeID,
  240. items: newItemData
  241. })
  242. }
  243. message.success(`${state.modalType === OptionModalType.ADD ? '新增' : '编辑'}成功`)
  244. return true
  245. } catch (error) {
  246. message.error(error)
  247. return false
  248. }
  249. }}>
  250. <ProFormText name="ID" hidden />
  251. <ProFormText
  252. name="name"
  253. label="选项名称"
  254. rules={[{ required: true, message: '请输入选项名称' }]}
  255. />
  256. <ProFormRadio.Group
  257. name="enable"
  258. label="状态"
  259. options={[
  260. { label: '启用', value: true },
  261. { label: '停用', value: false }
  262. ]}
  263. rules={[{ required: true, message: '请选择启用/停用' }]}
  264. />
  265. </ModalForm>
  266. </div>
  267. </div>
  268. )
  269. }
  270. export default Option