import * as React from 'react'; import { useState, useEffect, useCallback, useMemo, } from 'react'; import * as moment from 'moment'; import { Link } from 'react-router-dom'; import { FastImage, FastButton, FastDropdown, FastMenu, FastIcon, FastModal, } from 'components/ui'; import Action from 'components/basic/Action'; import { parseUrlTemplate, transformRequest, findLabel } from 'utils/tool'; import { Component } from 'components/types'; import fetchData from 'utils/fetchData'; import { App } from 'utils/types'; import { exportExcel } from 'utils/export'; import { TableTypes } from './types'; const DEFAULT_PAGE = 1; const DEFATUL_LIMIT = 10; const DEFAULT_TOTAL = 0; const DEFAULT_PAGEINFO = { page: DEFAULT_PAGE, limit: DEFATUL_LIMIT }; // const DEFATUL_ROW_SELECTION = { key: 'key', type: 'checkbox' }; const useTable = (props: TableTypes.TableProps) => { const { config, handle, compRef, dict, filterParams, onSetFilterParams, } = props; const { uuid, columns = [], scroll, action = [], pagination = {}, rowSelection, ajax, notInit, isInit, notPagination, listKey = 'list', totalKey = 'total', } = config; const { type: pageType, page: pageAlias, offset: offsetAlias, limit: limitAlias, } = pagination as TableTypes.Pagination; const isOffsetPagination = pageType === 'offset'; // 数据 const [dataSourceState, setDataSourceState] = useState([]); // 分页状态 const [pageInfoState, setPageInfoState] = useState(DEFAULT_PAGEINFO); // 数据总数 const [totalState, setTotalState] = useState(DEFAULT_TOTAL); // 表格栏目配置 const [columnsState, setColumnsState] = useState(columns); // 表格选中状态 const [selectedRowKeys, setSelectedRowKeys] = useState([]); // 表格选中状态存为对象 const [selectedRowObjs, setSelectedRowObjs] = useState([]); // 表单加载状态 const [loading, setLoading] = useState(false); // 清空多选 const handleInitSelectedRow = () => { setSelectedRowKeys([]); setSelectedRowObjs([]); }; // 根据配置,格式化分页参数 const formatPageInfo = useCallback((pageInfo: TableTypes.PageInfo) => { const { limit, page } = pageInfo; return { [(isOffsetPagination ? offsetAlias : pageAlias) as string]: isOffsetPagination ? (page - 1) * limit : page, [limitAlias]: limit, }; }, [isOffsetPagination, limitAlias, offsetAlias, pageAlias]); // 获取表格数据 const handleFetchData = useCallback( (params: App.Dict, pageNo?: number) => { setLoading(true); const newPageInfoState = { ...pageInfoState }; if (pageNo) { newPageInfoState.page = pageNo; setPageInfoState((state) => ({ ...state, page: pageNo, })); } const combinedParams = { ...formatPageInfo(newPageInfoState), ...filterParams, ...params, }; fetchData(ajax, combinedParams) .then((data: TableTypes.ResponseData | App.Dict[]) => { setLoading(false); if (notPagination) { // 无分页 setDataSourceState(data as App.Dict[]); setTotalState((data as App.Dict[]).length); } else { const total = (data as TableTypes.ResponseData)[totalKey]; const list = (data as TableTypes.ResponseData)[listKey]; setDataSourceState(list); setTotalState(total); } if (params) { delete params.pageNo; delete params.pageSize; onSetFilterParams({ ...filterParams, ...params }); } handleInitSelectedRow(); }) .catch(() => { setLoading(false); }); }, [ ajax, filterParams, formatPageInfo, notPagination, onSetFilterParams, pageInfoState, ] ); // pageNo改变回调 const handlePageNoChange = useCallback( (val: number) => { const { limit } = pageInfoState; const combinedParams = { ...filterParams, ...formatPageInfo(pageInfoState), [(isOffsetPagination ? offsetAlias : pageAlias) as string]: isOffsetPagination ? (val - 1) * limit : val, }; handleFetchData(combinedParams); setPageInfoState((state) => ({ ...state, page: val, })); }, [ formatPageInfo, handleFetchData, filterParams, isOffsetPagination, offsetAlias, pageAlias, pageInfoState, ] ); // pageSize改变回调 const handlePageSizeChange = useCallback( (pageSize: number) => { const combinedParams = { ...filterParams, ...formatPageInfo(pageInfoState), [limitAlias]: pageSize, }; handleFetchData(combinedParams); setPageInfoState((state) => ({ ...state, limit: pageSize, })); }, [filterParams, formatPageInfo, handleFetchData, limitAlias, pageInfoState] ); // page(pageNo, pageSize)改变回调 const handlePageChange = useCallback( (page: number, pageSize: number): void => { const combinedParams = { ...filterParams.current, ...formatPageInfo(pageInfoState), [(isOffsetPagination ? offsetAlias : pageAlias) as string]: isOffsetPagination ? (page - 1) * pageSize : page, [limitAlias]: pageSize || pageInfoState.limit, }; handleFetchData(combinedParams); setPageInfoState((state) => ({ ...state, page, limit: pageSize, })); }, [ filterParams, formatPageInfo, handleFetchData, isOffsetPagination, limitAlias, offsetAlias, pageAlias, pageInfoState, ] ); const handleColumnsChange = (_columns: App.Dict[]) => { setColumnsState(_columns); }; const handleSelectChange = useCallback( (_selectedRowKeys: string[], _selectedRows) => { setSelectedRowKeys(_selectedRowKeys); setSelectedRowObjs(_selectedRows); }, [] ); // 判断选中状态 const isSelectedRow = (actionConfig: Component.Business.Action) => { if (!selectedRowKeys.length) { FastModal.warning({ title: "提示", content: "请勾选操作项" }); return Promise.reject(); } const { ajaxKey } = actionConfig; const params = { [(ajaxKey as string) || (rowSelection as TableTypes.RowSelection).key]: selectedRowKeys.join(","), }; return Promise.resolve(params); }; // 导出 const handleExport = (_action: Component.Business.Action) => { const { exportType, fileName, ajax: exportAjax } = _action; if (exportType === Component.Business.ExportType.front) { exportExcel({ columns, fileName: fileName as string }, dataSourceState); } else { const { url, fixedParam } = exportAjax; const exportParams = { ...filterParams, ...fixedParam }; const exportUrl = `${url}?${transformRequest(exportParams)}`; // console.log('>>>>>>>>>>>', exportParams, exportUrl); window.open(exportUrl); } }; // 批量二次确认行为 const handleBatchConfirm = ( actionConfig: Component.Business.Action, params: App.Dict ) => { const { label, logText, logKey, ajax: ConfirmAjax } = actionConfig; return new Promise((resolve, reject) => { FastModal.confirm({ title: label, content: ( 确认 {logText || label} {selectedRowObjs .map((item: App.Dict) => item[logKey]) .filter((item: string) => item) .join(",")} 吗? ), onOk: () => { fetchData(ConfirmAjax, params).then(resolve); }, okText: "确认", cancelText: "取消", }); }); }; // 生成行为按钮 const createActionBtn = ( actionConfig: Component.Business.Action, data?: App.Dict ) => { const { type, mode, label } = actionConfig; let handleActionClick = null; if (type === Component.Business.TriggerType.export) { handleActionClick = handleExport; } else if ( type === Component.Business.TriggerType.confirm && mode === "batch" ) { handleActionClick = () => isSelectedRow(actionConfig).then((params: App.Dict) => handleBatchConfirm(actionConfig, params) ); } else if (mode === "batch") { handleActionClick = () => isSelectedRow(actionConfig); } return ( ); }; // 生成行为按钮 const createActionBtns = ( actionList: Component.Business.Action[], data?: App.Dict ) => actionList.map((actionItem: any) => { const { label, action: childAction } = actionItem; if (childAction) { return ( {childAction.map((childActionItem: any) => ( {createActionBtn(childActionItem, data)} ))} } > {label} ); } return createActionBtn(actionItem, data); }); // 更新表格配置项 const updateColumns = () => { const newColumns = columns.map((column: TableTypes.Column) => { const { type, separator, goto, target, format, action: actionList, alias, routeHash, } = column; return { ...column, render: { none: (val: string) => val, // 序号 sequence: (_val: string, _data: App.Dict, index: number) => index + 1 + (pageInfoState.page - 1) * 10, // 转义 alias: (val: string) => { if (alias) { return findLabel(val, dict[alias]) || val; } return val; }, // 时间 time: (val: string) => val && moment(val).format(format), // 图片 image: (val: string) => ( ), // 百分比 percent: (val: number) => `${(val / 100).toFixed(2)}%`, // 数组转字符串 arr2str: (val: string[]) => val.join(separator), // 字符串转数组 str2arr: (val: string) => val.split(separator as string).join("\n"), // 前端路由 route: (val: string, data: App.Dict) => { if (routeHash) { return {val}; } return ( {val} ); }, // 外链 link: (val: string, data: App.Dict) => ( {val} ), // html展示 // eslint-disable-next-line react/no-danger html: (val: string) => (
), // 操作 opts: (_val: string, data: App.Dict) => createActionBtns(actionList as Component.Business.Action[], data), }[type], }; }); setColumnsState(newColumns); }; // 向父组件暴露实例方法 const getInstance = () => { compRef.current = { ...compRef.current, [uuid]: { fetchData: handleFetchData, changePage: handlePageNoChange, }, }; }; // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(updateColumns, [columns, pageInfoState.page]); useEffect(getInstance, [compRef, handleFetchData, handlePageChange, uuid]); React.useEffect(() => { if (isInit) { handleFetchData({}); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return { notPagination, dataSource: dataSourceState, pageInfo: pageInfoState, total: totalState, columns: columnsState, scroll, actionList: action, selectedRowKeys, rowSelection, notInit, loading, handlers: useMemo(() => ({ handlePageNoChange, handlePageSizeChange, handlePageChange, handleColumnsChange, handleSelectChange, }), [ handlePageChange, handlePageNoChange, handlePageSizeChange, handleSelectChange, ]), createActionBtns, }; }; export default useTable;