index.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import {AutoColumn, AutoText, isNullOrTrue, Message} from "auto-antd";
  2. import {Button, Drawer, Modal, Switch, Table, TableProps} from "antd";
  3. import React, {useEffect, useMemo, useRef, useState} from "react";
  4. import {ColumnsType} from "antd/lib/table";
  5. import {CurdForm, ICurdForm, IProps as IFormProps} from "./Form.tsx";
  6. import {LDAP} from "../../api/ldap.ts";
  7. import {DeleteOutlined, EditOutlined, LoadingOutlined, PlusOutlined, SyncOutlined} from "@ant-design/icons";
  8. import './index.less'
  9. import {ICurdConfig, ICurdData} from "./types.ts";
  10. import {Cell} from "./cell";
  11. export type CurdColumn = AutoColumn & {
  12. search?: boolean
  13. editable?: boolean // 编辑时是否可编辑
  14. hidden?: boolean
  15. addable?: boolean // 添加时可编辑
  16. onlyAdd?: boolean // 仅新增时展示
  17. editHide?: boolean // 编辑时隐藏
  18. ellipsis?: boolean // 完整显示
  19. }
  20. type PageResp<T> = { total: number, list: T[], current: number, pageSize: number }
  21. type IProps<T> = IFormProps<T> & {
  22. columns: CurdColumn[],
  23. getList: (query: any) => Promise<PageResp<T> | undefined>,
  24. getDetail: (data: Partial<T>, add?: boolean) => Promise<T>
  25. onDelete?: (data: T) => Promise<any>
  26. config?: ICurdConfig<T>
  27. onSuccess?: (data: Partial<T>) => void
  28. operationRender?: React.ReactNode
  29. operation?: {
  30. add?: boolean
  31. delete?: boolean
  32. },
  33. expandable?: TableProps<T>['expandable']
  34. }
  35. /**
  36. * 一个完成的增删查改界面
  37. * @constructor
  38. */
  39. export function CurdPage<T extends ICurdData>(
  40. {
  41. columns,
  42. getList,
  43. config,
  44. operationRender,
  45. onSuccess,
  46. operation,
  47. expandable,
  48. ...props
  49. }: IProps<T>) {
  50. const [list, setList] = useState<T[]>([]);
  51. const [total, setTotal] = useState<number>(0)
  52. const [query, setQuery] = useState<any>({current: 1, pageSize: 10});
  53. const [loading, setLoading] = useState<boolean>(false);
  54. // edit or add
  55. const [visible, setVisible] = useState<boolean>(false);
  56. const [editData, setEditData] = useState<Partial<T>>()
  57. const formRef = useRef<ICurdForm<LDAP.Server>>()
  58. const onAddOrEdit = (edit: Partial<T>, add?: boolean) => {
  59. setVisible(true)
  60. if (props.getDetail) {
  61. setLoading(true)
  62. props.getDetail?.(edit,add)
  63. .then(res => {
  64. setEditData(res)
  65. })
  66. .catch(err => {
  67. Message.warning(err.message)
  68. setVisible(false)
  69. })
  70. .finally(() => {
  71. setLoading(false)
  72. })
  73. } else {
  74. setEditData({...edit})
  75. setVisible(true)
  76. }
  77. }
  78. const handleDelete = async (edit: T) => {
  79. Modal.confirm({
  80. type: 'warning',
  81. title: '您确定要删除该数据吗?',
  82. content: '删除操作不可撤销,请谨慎操作',
  83. onOk: async () => {
  84. setLoading(true)
  85. try {
  86. await props.onDelete?.(edit)
  87. setQuery((q: any)=>({...q}))
  88. }catch(err: any) {
  89. if(err instanceof Error){
  90. Message.warning(err.message)
  91. }else if (typeof err === "string") {
  92. Message.warning(err)
  93. }else if (err?.msg) {
  94. Message.warning('删除失败,错误信息为:'+ err.msg)
  95. }else {
  96. Message.warning('删除失败,错误信息为:'+ JSON.stringify(err,null,2))
  97. }
  98. }finally {
  99. setLoading(false)
  100. }
  101. }
  102. })
  103. }
  104. const tableColumns = useMemo(() => {
  105. const cols = columns.filter(item => !item.hidden)
  106. .map((column: CurdColumn) => {
  107. const col = {
  108. ...column,
  109. dataIndex: column.key,
  110. title: column.title,
  111. width: column.width,
  112. onCell: (record: Partial<T>) => {
  113. return {
  114. record,
  115. config: column,
  116. dataIndex: column.key,
  117. }
  118. }
  119. }
  120. if (col.type == 'switch' && !col.render){
  121. col.render = (value: boolean) => <Switch checked={value} disabled/>
  122. }
  123. // @ts-ignore
  124. if (col.type == 'select' && !col.render && column.mode!='tags'){
  125. col.render = (value: any) => <AutoText value={value} column={{...col}}/>
  126. }
  127. return col;
  128. }) as ColumnsType<T>
  129. cols.push({
  130. dataIndex: "",
  131. title: "操作",
  132. width: config?.operationWidth ?? 180,
  133. render: (_, record, index) => {
  134. return (<div className="operation-list">
  135. {
  136. isNullOrTrue(config?.editable) ?
  137. (<Button size="small" type="primary" icon={<EditOutlined/>}
  138. onClick={() => onAddOrEdit(record,false)}/>) : null
  139. }
  140. {
  141. operation?.add ? (<Button size="small" type="primary" icon={<PlusOutlined/>}
  142. onClick={() => onAddOrEdit(record,true)}/>) : null
  143. }
  144. {
  145. operation?.delete ? (<Button size="small" danger={true} icon={<DeleteOutlined/>}
  146. onClick={() => handleDelete(record)}/>) : null
  147. }
  148. {config?.operationRender?.(record, index, { handleDelete })}
  149. </div>)
  150. }
  151. })
  152. return cols;
  153. }, [columns]);
  154. const onClose = () => {
  155. setVisible(false)
  156. setEditData(undefined)
  157. }
  158. useEffect(() => {
  159. setLoading(true)
  160. getList(query).then(res => {
  161. if (res) {
  162. setList(res.list)
  163. setTotal(res.total)
  164. } else {
  165. Message.warning('无数据!')
  166. }
  167. }).finally(() => {
  168. setLoading(false)
  169. })
  170. }, [getList, query]);
  171. const onPageChange = (current: number, pageSize: number) => {
  172. setQuery((q: any) => ({...q, current, pageSize}))
  173. }
  174. const onSaveSuccess = (data: Partial<T>) => {
  175. setQuery({...query})
  176. onSuccess?.(data)
  177. setVisible(false)
  178. }
  179. return <div className="curd">
  180. <div className="curd-header">
  181. {
  182. config?.hideAdd ? null : (<Button onClick={() => onAddOrEdit({},true)} icon={<PlusOutlined/>}/>)
  183. }
  184. {operationRender}
  185. <div style={{flex: 1}}/>
  186. <Button loading={loading} onClick={() => setQuery({...query})} icon={<SyncOutlined/>}/>
  187. </div>
  188. <div className="curd-body">
  189. <Table columns={tableColumns as any}
  190. dataSource={list as any}
  191. pagination={ config?.pagination ? {
  192. total: total,
  193. pageSize: query.pageSize,
  194. current: query.current,
  195. onChange: onPageChange,
  196. }: { pageSize: query.pageSize }}
  197. components={{
  198. body: {
  199. cell: Cell,
  200. }
  201. }}
  202. size="middle"
  203. expandable={expandable}
  204. bordered={config?.bordered}
  205. rowKey={config?.rowKey ?? 'id'}
  206. />
  207. </div>
  208. <div className="curd-footer">
  209. </div>
  210. <Drawer open={visible} destroyOnClose width={config?.editDialogWidth ?? 650}
  211. title={editData?.id ? '编辑' : '新增'}
  212. height={400} onClose={onClose}>
  213. <div className="curd-edit-dialog">
  214. {
  215. loading ? (<LoadingOutlined/>) : (
  216. <CurdForm ref={formRef as any}
  217. initialData={editData}
  218. columns={columns}
  219. onSave={props.onSave as any}
  220. onSuccess={onSaveSuccess as any}
  221. onClose={onClose}
  222. config={config as any}/>
  223. )
  224. }
  225. </div>
  226. </Drawer>
  227. </div>
  228. }