You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

361 lines
13 KiB

  1. import { useStyle } from './style.ts'
  2. import { Badge, Button, Divider, Form, Popconfirm, Space, Tooltip } from 'antd'
  3. import { useAtom, useAtomValue, useSetAtom } from 'jotai'
  4. import { ModelContext, useSpanModel } from '@/store/r-form/model.ts'
  5. import { ReactNode, useEffect, useState } from 'react'
  6. import { transformAntdTableProColumns } from './utils'
  7. import Action from '@/components/action/Action.tsx'
  8. import { FilterOutlined } from '@ant-design/icons'
  9. import ListPageLayout from '@/layout/ListPageLayout.tsx'
  10. import { Table as ProTable } from '@/components/table'
  11. import { getValueCount, unSetColumnRules } from '@/utils'
  12. import { BetaSchemaForm, ProColumns, ProFormColumnsType } from '@ant-design/pro-components'
  13. import { useApiContext } from '@/context.ts'
  14. import { useDeepCompareEffect } from 'react-use'
  15. import { RFormTypes } from '@/types/r-form/model'
  16. import { ProCoreActionType } from '@ant-design/pro-utils/es/typing'
  17. import { getI18nTitle } from '@/i18n.ts'
  18. export interface RFormProps {
  19. title?: ReactNode
  20. namespace?: string
  21. columns?: ProColumns[] //重写columns
  22. actions?: ReactNode[] | JSX.Element[] //左上角的操作按钮
  23. toolbar?: ReactNode //工具栏
  24. renderActions?: (addAction: ReactNode) => ReactNode //渲染操作按钮
  25. resolveColumns?: (columns: ProColumns[]) => ProColumns[] //处理columns
  26. renderColumnOptions?: (record: any, defaultOptions: ReactNode[], index: number, action: ProCoreActionType | undefined) => ReactNode //渲染列的操作
  27. }
  28. const RForm = (
  29. {
  30. namespace,
  31. actions = [],
  32. toolbar,
  33. resolveColumns,
  34. renderActions,
  35. renderColumnOptions,
  36. columns: propColumns = [], title
  37. }: RFormProps) => {
  38. const { styles, cx } = useStyle()
  39. const apiCtx = useApiContext()
  40. const {
  41. apiAtom,
  42. deleteModelAtom,
  43. modelAtom,
  44. modelCURDAtom,
  45. modelsAtom,
  46. modelSearchAtom,
  47. saveOrUpdateModelAtom
  48. } = useSpanModel(namespace || apiCtx?.menu?.meta?.name || 'default') as ModelContext
  49. const [ form ] = Form.useForm()
  50. const [ filterForm ] = Form.useForm()
  51. const setApi = useSetAtom(apiAtom)
  52. const [ model, setModel ] = useAtom<RFormTypes.IModel>(modelAtom)
  53. const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateModelAtom)
  54. const [ search, setSearch ] = useAtom(modelSearchAtom)
  55. const { data, isFetching, isLoading, refetch } = useAtomValue(modelsAtom)
  56. const { mutate: deleteModel, isPending: isDeleting } = useAtomValue(deleteModelAtom)
  57. const { data: curdModal, isLoading: curdLoading, refetch: reloadCURDModal } = useAtomValue(modelCURDAtom)
  58. const [ open, setOpen ] = useState(false)
  59. const [ openFilter, setFilterOpen ] = useState(false)
  60. const [ searchKey, setSearchKey ] = useState(search?.key)
  61. const [ columns, setColumns ] = useState<ProColumns[]>([])
  62. useDeepCompareEffect(() => {
  63. let res = transformAntdTableProColumns(curdModal?.columns || [], propColumns, curdModal?.config?.i18n)
  64. if (resolveColumns) {
  65. res = resolveColumns(res)
  66. }
  67. const options = (record: any) => {
  68. return [
  69. <Action key="edit"
  70. as={'a'}
  71. onClick={() => {
  72. form.setFieldsValue(record)
  73. setOpen(true)
  74. }}>{'编辑'}</Action>,
  75. <Popconfirm
  76. key={'del_confirm'}
  77. disabled={isDeleting}
  78. onConfirm={() => {
  79. deleteModel([ record.id ])
  80. }}
  81. title={'确定要删除吗?'}>
  82. <a key="del">
  83. </a>
  84. </Popconfirm>
  85. ]
  86. }
  87. const _columns = [ {
  88. title: 'ID',
  89. dataIndex: 'id',
  90. hideInTable: true,
  91. hideInSearch: true,
  92. formItemProps: { hidden: true }
  93. } ].concat(res as any).concat([
  94. {
  95. title: getI18nTitle(curdModal?.config?.i18n, { dataIndex: 'option', title: '操作' },),
  96. dataIndex: 'option',
  97. valueType: 'option',
  98. fixed: 'right',
  99. render: (_, record, index, action) => {
  100. if (renderColumnOptions) {
  101. return renderColumnOptions(record, options(record), index, action)
  102. }
  103. return options(record)
  104. }
  105. } as any
  106. ])
  107. setColumns(_columns)
  108. }, [ curdModal?.columns, curdModal?.config?.i18n, propColumns, renderColumnOptions, resolveColumns, deleteModel, form, isDeleting, setOpen, ])
  109. useEffect(() => {
  110. if (apiCtx.isApi && apiCtx.api) {
  111. setApi(apiCtx.api)
  112. reloadCURDModal()
  113. }
  114. }, [ apiCtx.isApi, apiCtx.api ])
  115. useDeepCompareEffect(() => {
  116. setSearchKey(search?.key)
  117. filterForm.setFieldsValue(search)
  118. }, [ search ])
  119. useEffect(() => {
  120. if (isSuccess) {
  121. setOpen(false)
  122. }
  123. }, [ isSuccess ])
  124. const formProps = curdModal?.form.layoutType === 'DrawerForm' ? {
  125. layoutType: 'DrawerForm',
  126. drawerProps: {
  127. maskClosable: false,
  128. }
  129. } : {
  130. layoutType: 'ModalForm',
  131. modalProps: {
  132. maskClosable: false,
  133. }
  134. }
  135. const renderTitle = () => {
  136. if (title) {
  137. return title
  138. }
  139. if (apiCtx.menu) {
  140. const { menu } = apiCtx
  141. return menu.title
  142. }
  143. return null
  144. }
  145. const tableTitle = <>
  146. <Button key={'add'}
  147. onClick={() => {
  148. form.resetFields()
  149. form.setFieldsValue({
  150. id: 0,
  151. })
  152. setOpen(true)
  153. }}
  154. type={'primary'}>{getI18nTitle('actions.add','添加')}</Button>
  155. </>
  156. const _renderActions = () => {
  157. if (renderActions) {
  158. return renderActions(tableTitle)
  159. }
  160. return <Space>
  161. {[ tableTitle, ...actions ]}
  162. </Space>
  163. }
  164. return (
  165. <>
  166. <ListPageLayout
  167. className={styles.container}
  168. title={renderTitle()}>
  169. <ProTable
  170. {...curdModal?.table}
  171. rowKey="id"
  172. headerTitle={_renderActions()}
  173. toolbar={{
  174. /*search: {
  175. loading: isFetching && !!search?.key,
  176. onSearch: (value: string) => {
  177. setSearch(prev => ({
  178. ...prev,
  179. title: value
  180. }))
  181. },
  182. allowClear: true,
  183. onChange: (e) => {
  184. setSearchKey(e.target?.value)
  185. },
  186. value: searchKey,
  187. placeholder: '输入关键字搜索',
  188. },*/
  189. actions: [
  190. <Tooltip key={'filter'} title={getI18nTitle('actions.advanceSearch','高级查询')}>
  191. <Badge count={getValueCount(search)}>
  192. <Button
  193. onClick={() => {
  194. setFilterOpen(true)
  195. }}
  196. icon={<FilterOutlined/>} shape={'circle'} size={'small'}/>
  197. </Badge>
  198. </Tooltip>,
  199. <Divider type={'vertical'} key={'divider'}/>,
  200. ]
  201. }}
  202. scroll={{
  203. x: (columns?.length || 1) * 100,
  204. y: 'calc(100vh - 290px)'
  205. }}
  206. search={false}
  207. onRow={(record) => {
  208. return {
  209. className: cx({
  210. // 'ant-table-row-selected': currentMovie?.id === record.id
  211. }),
  212. onClick: () => {
  213. setModel(record)
  214. }
  215. }
  216. }}
  217. dateFormatter="string"
  218. loading={isLoading || isFetching || curdLoading}
  219. dataSource={data?.rows ?? []}
  220. columns={columns}
  221. options={{
  222. reload: () => {
  223. refetch()
  224. },
  225. }}
  226. pagination={{
  227. total: data?.total,
  228. pageSize: search.pageSize,
  229. current: search.page,
  230. onShowSizeChange: (current: number, size: number) => {
  231. setSearch({
  232. ...search,
  233. pageSize: size,
  234. page: current
  235. })
  236. },
  237. onChange: (current, pageSize) => {
  238. setSearch(prev => {
  239. return {
  240. ...prev,
  241. page: current,
  242. pageSize: pageSize,
  243. }
  244. })
  245. },
  246. }}
  247. />
  248. <BetaSchemaForm
  249. {...curdModal?.form}
  250. grid={true}
  251. shouldUpdate={false}
  252. width={1000}
  253. form={form}
  254. layout={'vertical'}
  255. scrollToFirstError={true}
  256. title={model?.id !== 0 ? getI18nTitle('actions.edit','编辑') : getI18nTitle('actions.add','添加')}
  257. {...formProps as any}
  258. open={open}
  259. onOpenChange={(open) => {
  260. setOpen(open)
  261. }}
  262. loading={isSubmitting}
  263. onFinish={async (values) => {
  264. console.log(values)
  265. saveOrUpdate(values as any)
  266. }}
  267. columns={columns as ProFormColumnsType[]}/>
  268. <BetaSchemaForm
  269. {...curdModal?.form}
  270. title={getI18nTitle('actions.advanceSearch','高级查询')}
  271. grid={true}
  272. shouldUpdate={false}
  273. width={500}
  274. form={filterForm}
  275. open={openFilter}
  276. onOpenChange={open => {
  277. setFilterOpen(open)
  278. }}
  279. layout={'vertical'}
  280. scrollToFirstError={true}
  281. layoutType={formProps.layoutType as any}
  282. drawerProps={{
  283. ...formProps.drawerProps,
  284. mask: false,
  285. }}
  286. modalProps={{
  287. ...formProps.modalProps,
  288. mask: false,
  289. }}
  290. submitter={{
  291. searchConfig: {
  292. resetText: getI18nTitle('actions.clear', '清空'),
  293. submitText: getI18nTitle('actions.search', '查询'),
  294. },
  295. onReset: () => {
  296. filterForm.resetFields()
  297. },
  298. render: (props,) => {
  299. return (
  300. <div style={{ textAlign: 'right' }}>
  301. <Space>
  302. <Button onClick={() => {
  303. props.reset()
  304. }}>{props.searchConfig?.resetText}</Button>
  305. <Button type="primary"
  306. onClick={() => {
  307. props.submit()
  308. }}
  309. >{props.searchConfig?.submitText}</Button>
  310. </Space>
  311. </div>
  312. )
  313. },
  314. }}
  315. onFinish={async (values: any) => {
  316. //处理,变成数组
  317. Object.keys(values).forEach(key => {
  318. if (typeof values[key] === 'string' && values[key].includes(',')) {
  319. values[key] = values[key].split(',')
  320. }
  321. })
  322. setSearch(values)
  323. }}
  324. columns={unSetColumnRules(columns.filter(item => !item.hideInSearch) as ProFormColumnsType[])}/>
  325. </ListPageLayout>
  326. </>
  327. )
  328. }
  329. export default RForm