index.tsx 9.1 KB

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