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

10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
  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