diff --git a/src/components/common/TreePanel.tsx b/src/components/common/TreePanel.tsx index 57caf999..d4b8ec0e 100644 --- a/src/components/common/TreePanel.tsx +++ b/src/components/common/TreePanel.tsx @@ -2,6 +2,7 @@ import type { DataNode, EventDataNode } from 'antd/es/tree'; import { LeftOutlined, SearchOutlined } from '@ant-design/icons'; import { Button, Card, Input, Tree } from 'antd'; import { useEffect, useMemo, useState, type Key } from 'react'; +import { collectTreeKeys } from '@/utils/ruoyi'; export interface TreePanelProps> { title: string; @@ -60,10 +61,6 @@ function toDataNodes( }); } -function collectKeys(nodes: DataNode[]): Key[] { - return nodes.flatMap(node => [node.key, ...(node.children?.length ? collectKeys(node.children) : [])]); -} - export default function TreePanel>({ title, placeholder = '请输入名称', @@ -95,7 +92,7 @@ export default function TreePanel>({ () => toDataNodes(filteredData, mergedFieldNames, disabledField), [disabledField, filteredData, mergedFieldNames] ); - const allTreeKeys = useMemo(() => collectKeys(treeData), [treeData]); + const allTreeKeys = useMemo(() => collectTreeKeys(treeData, node => node.key), [treeData]); const mergedCollapsed = collapsed ?? innerCollapsed; useEffect(() => { diff --git a/src/components/workflow/ProcessMeddle.tsx b/src/components/workflow/ProcessMeddle.tsx index d9adb1b0..f07dafe0 100644 --- a/src/components/workflow/ProcessMeddle.tsx +++ b/src/components/workflow/ProcessMeddle.tsx @@ -6,6 +6,8 @@ import type { UserVO } from '@/api/system/user/types'; import type { FlowTaskVO } from '@/api/workflow/task/types'; import { currentTaskAllUser, getTask, taskOperation, terminationTask } from '@/api/workflow/task'; import UserSelect from '@/components/common/UserSelect'; +import { useLoading } from '@/hooks/useLoading'; +import { confirmTitleSafe } from '@/utils/modal'; type UserSelectMode = 'transfer' | 'addSignature'; @@ -24,7 +26,7 @@ interface ProcessMeddleProps { } export default function ProcessMeddle({ open, taskId, width = 760, onOpenChange, onSuccess }: ProcessMeddleProps) { - const [loading, setLoading] = useState(false); + const { loading, withLoading } = useLoading(); const [currentTask, setCurrentTask] = useState(); const [userModalOpen, { setTrue: openUserModal, setFalse: closeUserModal }] = useBoolean(false); const [userSelectMode, setUserSelectMode] = useState('transfer'); @@ -36,19 +38,15 @@ export default function ProcessMeddle({ open, taskId, width = 760, onOpenChange, return; } - const loadTask = async () => { - setLoading(true); - setCurrentTask(undefined); - try { + const loadTask = () => + withLoading(async () => { + setCurrentTask(undefined); const res = await getTask(taskId); setCurrentTask(res.data); - } finally { - setLoading(false); - } - }; + }); loadTask(); - }, [open, taskId]); + }, [open, taskId, withLoading]); const closeWithSuccess = () => { onOpenChange(false); @@ -74,18 +72,14 @@ export default function ProcessMeddle({ open, taskId, width = 760, onOpenChange, message.warning('请选择用户'); return; } - Modal.confirm({ - title: '是否确认提交?', - onOk: async () => { - await taskOperation( - { taskId: currentTask.id, userId: user.userId, message: '', messageType: ['1'] }, - 'transferTask' - ); - message.success('操作成功'); - closeUserModal(); - closeWithSuccess(); - } - }); + if (!(await confirmTitleSafe('是否确认提交?'))) return; + await taskOperation( + { taskId: currentTask.id, userId: user.userId, message: '', messageType: ['1'] }, + 'transferTask' + ); + message.success('操作成功'); + closeUserModal(); + closeWithSuccess(); return; } @@ -94,55 +88,40 @@ export default function ProcessMeddle({ open, taskId, width = 760, onOpenChange, message.warning('请选择用户'); return; } - Modal.confirm({ - title: '是否确认提交?', - onOk: async () => { - await taskOperation({ taskId: currentTask.id, userIds, message: '', messageType: ['1'] }, 'addSignature'); - message.success('操作成功'); - closeUserModal(); - closeWithSuccess(); - } - }); + if (!(await confirmTitleSafe('是否确认提交?'))) return; + await taskOperation({ taskId: currentTask.id, userIds, message: '', messageType: ['1'] }, 'addSignature'); + message.success('操作成功'); + closeUserModal(); + closeWithSuccess(); }; const openReductionSignature = async () => { if (!currentTask?.id) return; - setLoading(true); - try { + await withLoading(async () => { const res = await currentTaskAllUser(currentTask.id); setSignatureUsers((res.data || []).map(item => ({ ...item, nodeName: currentTask.nodeName }))); openSignatureModal(); - } finally { - setLoading(false); - } + }); }; const deleteSignatureUser = async (row: SignatureUser) => { if (!currentTask?.id) return; - Modal.confirm({ - title: '是否确认提交?', - onOk: async () => { - await taskOperation( - { taskId: currentTask.id, userIds: [row.userId], message: '', messageType: ['1'] }, - 'reductionSignature' - ); - message.success('操作成功'); - closeSignatureModal(); - closeWithSuccess(); - } - }); + if (!(await confirmTitleSafe('是否确认提交?'))) return; + await taskOperation( + { taskId: currentTask.id, userIds: [row.userId], message: '', messageType: ['1'] }, + 'reductionSignature' + ); + message.success('操作成功'); + closeSignatureModal(); + closeWithSuccess(); }; const submitTermination = async () => { if (!currentTask?.id) return; - Modal.confirm({ - title: '是否确认终止?', - onOk: async () => { - await terminationTask({ taskId: currentTask.id, comment: '' }); - message.success('操作成功'); - closeWithSuccess(); - } - }); + if (!(await confirmTitleSafe('是否确认终止?'))) return; + await terminationTask({ taskId: currentTask.id, comment: '' }); + message.success('操作成功'); + closeWithSuccess(); }; return ( diff --git a/src/components/workflow/SubmitVerify.tsx b/src/components/workflow/SubmitVerify.tsx index d62f7a87..9488a5f2 100644 --- a/src/components/workflow/SubmitVerify.tsx +++ b/src/components/workflow/SubmitVerify.tsx @@ -25,6 +25,8 @@ import { import FileUpload from '@/components/common/FileUpload'; import UserSelect from '@/components/common/UserSelect'; import MessageType from '@/components/workflow/MessageType'; +import { useLoading } from '@/hooks/useLoading'; +import { confirmTitleSafe } from '@/utils/modal'; type UserSelectMode = 'copy' | 'assigneeMap' | 'transfer' | 'delegate' | 'addSignature'; @@ -46,16 +48,6 @@ function buttonVisible(task?: FlowTaskVO, code?: string) { return !!task?.buttonList?.find(item => item.code === code && item.show); } -function confirmAction(title: string) { - return new Promise(resolve => { - Modal.confirm({ - title, - onOk: () => resolve(true), - onCancel: () => resolve(false) - }); - }); -} - export default function SubmitVerify({ open, taskId, variables = {}, onOpenChange, onSubmitted }: SubmitVerifyProps) { const [approveForm] = Form.useForm<{ message?: string; @@ -64,7 +56,7 @@ export default function SubmitVerify({ open, taskId, variables = {}, onOpenChang fileId?: string; }>(); const [backForm] = Form.useForm<{ nodeCode?: string; message?: string; messageType?: string[]; fileId?: string }>(); - const [approvalLoading, setApprovalLoading] = useState(false); + const { loading: approvalLoading, setLoading: setApprovalLoading, withLoading: withApprovalLoading } = useLoading(); const [currentTask, setCurrentTask] = useState(); const [nextNodes, setNextNodes] = useState([]); const [backOpen, { setTrue: openBackModal, setFalse: closeBackModal }] = useBoolean(false); @@ -121,7 +113,7 @@ export default function SubmitVerify({ open, taskId, variables = {}, onOpenChang openUserModal(); }; - const submitUserSelect = (users: UserVO[]) => { + const submitUserSelect = async (users: UserVO[]) => { if (userSelectMode === 'copy') { setCopyUsers(users); closeUserModal(); @@ -155,25 +147,21 @@ export default function SubmitVerify({ open, taskId, variables = {}, onOpenChang message.warning('请选择用户'); return; } - Modal.confirm({ - title: '是否确认提交?', - onOk: async () => { - const values = approveForm.getFieldsValue(); - await taskOperation( - { - taskId: currentTask.id, - userId: user.userId, - message: values.message, - messageType: values.messageType || ['1'], - variables - }, - userSelectMode === 'transfer' ? 'transferTask' : 'delegateTask' - ); - message.success('操作成功'); - closeUserModal(); - finishWorkflowAction(); - } - }); + if (!(await confirmTitleSafe('是否确认提交?'))) return; + const values = approveForm.getFieldsValue(); + await taskOperation( + { + taskId: currentTask.id, + userId: user.userId, + message: values.message, + messageType: values.messageType || ['1'], + variables + }, + userSelectMode === 'transfer' ? 'transferTask' : 'delegateTask' + ); + message.success('操作成功'); + closeUserModal(); + finishWorkflowAction(); return; } @@ -182,25 +170,21 @@ export default function SubmitVerify({ open, taskId, variables = {}, onOpenChang message.warning('请选择用户'); return; } - Modal.confirm({ - title: '是否确认提交?', - onOk: async () => { - const values = approveForm.getFieldsValue(); - await taskOperation( - { - taskId: currentTask.id, - userIds, - message: values.message, - messageType: values.messageType || ['1'], - variables - }, - 'addSignature' - ); - message.success('操作成功'); - closeUserModal(); - finishWorkflowAction(); - } - }); + if (!(await confirmTitleSafe('是否确认提交?'))) return; + const values = approveForm.getFieldsValue(); + await taskOperation( + { + taskId: currentTask.id, + userIds, + message: values.message, + messageType: values.messageType || ['1'], + variables + }, + 'addSignature' + ); + message.success('操作成功'); + closeUserModal(); + finishWorkflowAction(); }; const removeCopyUser = (userId?: string | number) => { @@ -210,9 +194,8 @@ export default function SubmitVerify({ open, taskId, variables = {}, onOpenChang const completeApproval = async () => { if (!currentTask?.id) return; const values = await approveForm.validateFields(); - if (!(await confirmAction('是否确认提交?'))) return; - setApprovalLoading(true); - try { + if (!(await confirmTitleSafe('是否确认提交?'))) return; + await withApprovalLoading(async () => { await completeTask({ taskId: currentTask.id, message: values.message, @@ -224,16 +207,13 @@ export default function SubmitVerify({ open, taskId, variables = {}, onOpenChang }); message.success('操作成功'); finishWorkflowAction(); - } finally { - setApprovalLoading(false); - } + }); }; const openBack = async () => { if (!currentTask?.id) return; openBackModal(); - setApprovalLoading(true); - try { + await withApprovalLoading(async () => { const res = await getBackTaskNode(currentTask.id, currentTask.nodeCode); setBackNodes(res.data || []); backForm.setFieldsValue({ @@ -242,17 +222,14 @@ export default function SubmitVerify({ open, taskId, variables = {}, onOpenChang messageType: ['1'], fileId: undefined }); - } finally { - setApprovalLoading(false); - } + }); }; const submitBack = async () => { if (!currentTask?.id) return; const values = await backForm.validateFields(); - if (!(await confirmAction('是否确认驳回?'))) return; - setApprovalLoading(true); - try { + if (!(await confirmTitleSafe('是否确认驳回?'))) return; + await withApprovalLoading(async () => { await backProcess({ ...values, taskId: currentTask.id, @@ -261,58 +238,46 @@ export default function SubmitVerify({ open, taskId, variables = {}, onOpenChang message.success('操作成功'); closeBackModal(); finishWorkflowAction(); - } finally { - setApprovalLoading(false); - } + }); }; const submitTermination = async () => { if (!currentTask?.id) return; const values = approveForm.getFieldsValue(); - if (!(await confirmAction('是否确认终止?'))) return; - setApprovalLoading(true); - try { + if (!(await confirmTitleSafe('是否确认终止?'))) return; + await withApprovalLoading(async () => { await terminationTask({ taskId: currentTask.id, comment: values.message }); message.success('操作成功'); finishWorkflowAction(); - } finally { - setApprovalLoading(false); - } + }); }; const openReductionSignature = async () => { if (!currentTask?.id) return; - setApprovalLoading(true); - try { + await withApprovalLoading(async () => { const res = await currentTaskAllUser(currentTask.id); setSignatureUsers((res.data || []).map(item => ({ ...item, nodeName: currentTask.nodeName }))); openSignatureModal(); - } finally { - setApprovalLoading(false); - } + }); }; const deleteSignatureUser = async (row: SignatureUser) => { if (!currentTask?.id) return; const values = approveForm.getFieldsValue(); - Modal.confirm({ - title: '是否确认提交?', - onOk: async () => { - await taskOperation( - { - taskId: currentTask.id, - userIds: [row.userId], - message: values.message, - messageType: values.messageType || ['1'], - variables - }, - 'reductionSignature' - ); - message.success('操作成功'); - closeSignatureModal(); - finishWorkflowAction(); - } - }); + if (!(await confirmTitleSafe('是否确认提交?'))) return; + await taskOperation( + { + taskId: currentTask.id, + userIds: [row.userId], + message: values.message, + messageType: values.messageType || ['1'], + variables + }, + 'reductionSignature' + ); + message.success('操作成功'); + closeSignatureModal(); + finishWorkflowAction(); }; return ( diff --git a/src/hooks/useDateRangeQuery.ts b/src/hooks/useDateRangeQuery.ts new file mode 100644 index 00000000..c953aae7 --- /dev/null +++ b/src/hooks/useDateRangeQuery.ts @@ -0,0 +1,25 @@ +import { useCallback, useState } from 'react'; +import type { ConfigType } from 'dayjs'; +import { addDateRange, formatDateTimeRange } from '@/utils/ruoyi'; + +export type DateRangeValue = [ConfigType, ConfigType] | null | undefined; + +export function useDateRangeQuery(propName?: string) { + const [dateRange, setDateRange] = useState(); + + const applyDateRange = useCallback( + >(queryParams: T, range: DateRangeValue = dateRange) => { + return addDateRange(queryParams, formatDateTimeRange(range), propName); + }, + [dateRange, propName] + ); + + const resetDateRange = useCallback(() => setDateRange(undefined), []); + + return { + dateRange, + setDateRange, + applyDateRange, + resetDateRange + }; +} diff --git a/src/hooks/useLoading.ts b/src/hooks/useLoading.ts new file mode 100644 index 00000000..beb8e520 --- /dev/null +++ b/src/hooks/useLoading.ts @@ -0,0 +1,20 @@ +import { useCallback, useState } from 'react'; + +export function useLoading(initialValue = false) { + const [loading, setLoading] = useState(initialValue); + + const withLoading = useCallback(async (task: () => Promise) => { + setLoading(true); + try { + return await task(); + } finally { + setLoading(false); + } + }, []); + + return { + loading, + setLoading, + withLoading + }; +} diff --git a/src/hooks/useSearchReset.ts b/src/hooks/useSearchReset.ts new file mode 100644 index 00000000..f2a50876 --- /dev/null +++ b/src/hooks/useSearchReset.ts @@ -0,0 +1,9 @@ +import { useCallback, type RefObject } from 'react'; +import type { ActionType } from '@ant-design/pro-components'; + +export function useSearchReset(actionRef: RefObject, resetExtras?: () => void) { + return useCallback(() => { + resetExtras?.(); + setTimeout(() => actionRef.current?.reloadAndRest?.(), 0); + }, [actionRef, resetExtras]); +} diff --git a/src/hooks/useTreeTableExpand.ts b/src/hooks/useTreeTableExpand.ts new file mode 100644 index 00000000..45391a5c --- /dev/null +++ b/src/hooks/useTreeTableExpand.ts @@ -0,0 +1,54 @@ +import { useCallback, useState } from 'react'; +import type { Key } from 'react'; +import { collectTreeKeys } from '@/utils/ruoyi'; + +interface UseTreeTableExpandOptions { + initialExpandAll?: boolean; + getChildren?: (row: T) => T[] | undefined; +} + +export function useTreeTableExpand( + getKey: (row: T) => Key, + options: UseTreeTableExpandOptions = {} +) { + const { initialExpandAll = false, getChildren } = options; + const [expandAll, setExpandAll] = useState(initialExpandAll); + const [expandedRowKeys, setExpandedRowKeys] = useState([]); + + const getAllKeys = useCallback( + (rows: T[]) => collectTreeKeys(rows, getKey, getChildren), + [getChildren, getKey] + ); + + const syncExpandedRows = useCallback( + (rows: T[], expanded = expandAll) => { + setExpandedRowKeys(expanded ? getAllKeys(rows) : []); + }, + [expandAll, getAllKeys] + ); + + const toggleExpandAll = useCallback( + (rows: T[]) => { + setExpandAll(current => { + const next = !current; + setExpandedRowKeys(next ? getAllKeys(rows) : []); + return next; + }); + }, + [getAllKeys] + ); + + const onExpandedRowsChange = useCallback((keys: readonly Key[]) => { + setExpandedRowKeys([...keys]); + }, []); + + return { + expandAll, + expandedRowKeys, + setExpandAll, + setExpandedRowKeys, + syncExpandedRows, + toggleExpandAll, + onExpandedRowsChange + }; +} diff --git a/src/pages/demo/tree/index.tsx b/src/pages/demo/tree/index.tsx index 8a5bc05c..12542348 100644 --- a/src/pages/demo/tree/index.tsx +++ b/src/pages/demo/tree/index.tsx @@ -1,4 +1,3 @@ -import type { Key } from 'react'; import { DeleteOutlined, EditOutlined, PlusOutlined, SortAscendingOutlined } from '@ant-design/icons'; import { ModalForm, @@ -15,6 +14,7 @@ import { useMemo, useRef, useState } from 'react'; import type { TreeForm, TreeQuery, TreeVO } from '@/api/demo/tree/types'; import { addTree, delTree, getTree, listTree, updateTree } from '@/api/demo/tree'; import RowActions from '@/components/common/RowActions'; +import { useTreeTableExpand } from '@/hooks/useTreeTableExpand'; import { useUserStore } from '@/stores/userStore'; import { hasPermi } from '@/utils/permission'; import { handleTree } from '@/utils/ruoyi'; @@ -35,18 +35,14 @@ function toTreeSelectData(nodes: TreeVO[]): TreeSelectNode[] { })); } -function collectTreeKeys(nodes: TreeVO[]): Key[] { - return nodes.flatMap(node => [node.id, ...(node.children ? collectTreeKeys(node.children) : [])]); -} - export default function DemoTreePage() { const actionRef = useRef(undefined); const [form] = Form.useForm(); const userInfo = useUserStore(state => state.userInfo); const [treeOptions, setTreeOptions] = useState([]); const [tableRows, setTableRows] = useState([]); - const [expandAll, setExpandAll] = useState(false); - const [expandedRowKeys, setExpandedRowKeys] = useState([]); + const { expandAll, expandedRowKeys, onExpandedRowsChange, syncExpandedRows, toggleExpandAll } = + useTreeTableExpand(row => row.id); const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false); const [modalTitle, setModalTitle] = useState(''); @@ -134,13 +130,13 @@ export default function DemoTreePage() { search={{ labelWidth: 90 }} expandable={{ expandedRowKeys, - onExpandedRowsChange: keys => setExpandedRowKeys([...keys]) + onExpandedRowsChange }} request={async params => { const res = await listTree({ treeName: params.treeName }); const rows = handleTree(res.data || [], 'id', 'parentId'); setTableRows(rows); - setExpandedRowKeys(expandAll ? collectTreeKeys(rows) : []); + syncExpandedRows(rows, expandAll); return { data: rows, total: rows.length, success: true }; }} toolbar={{ title: '测试树列表' }} @@ -153,13 +149,7 @@ export default function DemoTreePage() { diff --git a/src/pages/monitor/cache/index.tsx b/src/pages/monitor/cache/index.tsx index e7969a32..65c3894f 100644 --- a/src/pages/monitor/cache/index.tsx +++ b/src/pages/monitor/cache/index.tsx @@ -6,6 +6,7 @@ import ReactECharts from 'echarts-for-react'; import { Fragment, useCallback, useEffect, useMemo, useState } from 'react'; import type { CacheVO } from '@/api/monitor/cache/types'; import { getCache } from '@/api/monitor/cache'; +import { useLoading } from '@/hooks/useLoading'; function infoValue(cache: Partial, key: string) { return cache.info?.[key] || '-'; @@ -44,17 +45,14 @@ function overviewRows(cache: Partial) { export default function MonitorCachePage() { const [cache, setCache] = useState>({}); - const [loading, setLoading] = useState(false); + const { loading, withLoading } = useLoading(); const loadOverview = useCallback(async () => { - setLoading(true); - try { + await withLoading(async () => { const cacheRes = await getCache(); setCache(cacheRes.data || {}); - } finally { - setLoading(false); - } - }, []); + }); + }, [withLoading]); useEffect(() => { loadOverview(); diff --git a/src/pages/monitor/logininfo/index.tsx b/src/pages/monitor/logininfo/index.tsx index 0ed38bd0..b94883e4 100644 --- a/src/pages/monitor/logininfo/index.tsx +++ b/src/pages/monitor/logininfo/index.tsx @@ -3,20 +3,18 @@ import { PageContainer, ProTable, type ActionType, type ProColumns } from '@ant- import { Button, message, Popconfirm } from 'antd'; import { useMemo, useRef } from 'react'; import type { LoginInfoQuery, LoginInfoVO } from '@/api/monitor/logininfo/types'; -import type { DictData } from '@/api/system/dict/data/types'; import { cleanLoginInfo, delLoginInfo, listLoginInfo, unlockLoginInfo } from '@/api/monitor/logininfo'; import DictTag from '@/components/common/DictTag'; import EllipsisText from '@/components/common/EllipsisText'; +import { useDateRangeQuery } from '@/hooks/useDateRangeQuery'; import { useDict } from '@/hooks/useDict'; import { useTableExport } from '@/hooks/useTableExport'; import { useTableSelection } from '@/hooks/useTableSelection'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; import { hasPermi } from '@/utils/permission'; -import { addDateRange, formatDateTimeRange, toPageQuery, toTableData, withTableSort } from '@/utils/ruoyi'; +import { toPageQuery, toTableData, withTableSort } from '@/utils/ruoyi'; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} export default function MonitorLoginInfoPage() { const actionRef = useRef(undefined); @@ -26,6 +24,7 @@ export default function MonitorLoginInfoPage() { row => row.infoId ); const { updateExportParams, exportFile } = useTableExport(); + const { applyDateRange: applyLoginTimeDateRange } = useDateRangeQuery(); const canRemove = hasPermi(userInfo, ['monitor:logininfo:remove']); const canUnlock = hasPermi(userInfo, ['monitor:logininfo:unlock']); @@ -136,9 +135,9 @@ export default function MonitorLoginInfoPage() { rowSelection={{ selectedRowKeys: ids, onChange: handleSelectionChange }} request={async (params, sort) => { const { loginTimeRange, ...tableParams } = params; - const query = addDateRange( + const query = applyLoginTimeDateRange( withTableSort(toPageQuery(tableParams), sort, { orderByColumn: 'loginTime', isAsc: 'descending' }), - formatDateTimeRange(loginTimeRange) + loginTimeRange ); updateExportParams(query); const res = await listLoginInfo(query); diff --git a/src/pages/monitor/online/index.tsx b/src/pages/monitor/online/index.tsx index 6eeb9715..e5c06b8a 100644 --- a/src/pages/monitor/online/index.tsx +++ b/src/pages/monitor/online/index.tsx @@ -3,19 +3,16 @@ import { PageContainer, ProTable, type ActionType, type ProColumns } from '@ant- import { message } from 'antd'; import { useMemo, useRef } from 'react'; import type { OnlineQuery, OnlineVO } from '@/api/monitor/online/types'; -import type { DictData } from '@/api/system/dict/data/types'; import { forceLogout, listOnline } from '@/api/monitor/online'; import DictTag from '@/components/common/DictTag'; import EllipsisText from '@/components/common/EllipsisText'; import RowActions from '@/components/common/RowActions'; import { useDict } from '@/hooks/useDict'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; import { hasPermi } from '@/utils/permission'; import { toPageQuery, toTableData } from '@/utils/ruoyi'; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} export default function MonitorOnlinePage() { const actionRef = useRef(undefined); diff --git a/src/pages/monitor/operlog/index.tsx b/src/pages/monitor/operlog/index.tsx index 14178bef..fbff4345 100644 --- a/src/pages/monitor/operlog/index.tsx +++ b/src/pages/monitor/operlog/index.tsx @@ -10,16 +10,15 @@ import DictTag from '@/components/common/DictTag'; import EllipsisText from '@/components/common/EllipsisText'; import JsonViewer from '@/components/common/JsonViewer'; import RowActions from '@/components/common/RowActions'; +import { useDateRangeQuery } from '@/hooks/useDateRangeQuery'; import { useDict } from '@/hooks/useDict'; import { useTableExport } from '@/hooks/useTableExport'; import { useTableSelection } from '@/hooks/useTableSelection'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; import { hasPermi } from '@/utils/permission'; -import { addDateRange, formatDateTimeRange, toPageQuery, toTableData, withTableSort } from '@/utils/ruoyi'; +import { toPageQuery, toTableData, withTableSort } from '@/utils/ruoyi'; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} function dictText(dicts: DictData[] | undefined, value?: string | number) { const normalized = value === undefined || value === null ? undefined : String(value); @@ -34,6 +33,7 @@ export default function MonitorOperlogPage() { const [detailOpen, { setTrue: openDetailModal, setFalse: closeDetailModal }] = useBoolean(false); const [detail, setDetail] = useState(); const { updateExportParams, exportFile } = useTableExport(); + const { applyDateRange: applyOperTimeDateRange } = useDateRangeQuery(); const canQuery = hasPermi(userInfo, ['monitor:operlog:query']); const canRemove = hasPermi(userInfo, ['monitor:operlog:remove']); @@ -167,9 +167,9 @@ export default function MonitorOperlogPage() { rowSelection={{ selectedRowKeys: ids, onChange: handleSelectionChange }} request={async (params, sort) => { const { operTimeRange, ...tableParams } = params; - const query = addDateRange( + const query = applyOperTimeDateRange( withTableSort(toPageQuery(tableParams), sort, { orderByColumn: 'operTime', isAsc: 'descending' }), - formatDateTimeRange(operTimeRange) + operTimeRange ); updateExportParams(query); const res = await listOperlog(query); diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 8a85eba0..dbfaed86 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -8,6 +8,7 @@ import { useCallback, useEffect, useState } from 'react'; import type { RegisterParams, VerifyCodeResult } from '@/api/types'; import { getCodeImg, register } from '@/api/login'; import LocaleSelect from '@/components/layout/LocaleSelect'; +import { useLoading } from '@/hooks/useLoading'; import { useAppStore } from '@/stores/appStore'; import { appEnv } from '@/utils/env'; @@ -70,7 +71,7 @@ export default function Register() { const appLocale = useAppStore(state => state.appLocale); const setAppLocale = useAppStore(state => state.setAppLocale); const [captcha, setCaptcha] = useState({ captchaEnabled: true }); - const [loading, setLoading] = useState(false); + const { loading, withLoading } = useLoading(); const text = registerText[appLocale]; const loadCaptcha = useCallback(async () => { @@ -90,26 +91,25 @@ export default function Register() { }, []); const submitRegister = async (values: RegisterParams) => { - setLoading(true); - try { - await register({ - ...values, - uuid: captcha.uuid, - userType: 'sys_user' - }); - await Modal.success({ - title: text.successTitle, - content: text.success(values.username) - }); - history.push('/login'); - } catch (error) { - if (captcha.captchaEnabled) { - loadCaptcha(); + await withLoading(async () => { + try { + await register({ + ...values, + uuid: captcha.uuid, + userType: 'sys_user' + }); + await Modal.success({ + title: text.successTitle, + content: text.success(values.username) + }); + history.push('/login'); + } catch (error) { + if (captcha.captchaEnabled) { + loadCaptcha(); + } + message.error((error as Error).message || text.fail); } - message.error((error as Error).message || text.fail); - } finally { - setLoading(false); - } + }); }; return ( diff --git a/src/pages/system/client/index.tsx b/src/pages/system/client/index.tsx index 3efaeefe..5ebd8ec1 100644 --- a/src/pages/system/client/index.tsx +++ b/src/pages/system/client/index.tsx @@ -12,10 +12,9 @@ import { type ProColumns } from '@ant-design/pro-components'; import { useBoolean } from 'ahooks'; -import { Button, Form, message, Modal, Popconfirm, Space, Switch } from 'antd'; +import { Button, Form, message, Popconfirm, Space, Switch } from 'antd'; import { useMemo, useRef, useState } from 'react'; import type { ClientForm, ClientQuery, ClientVO } from '@/api/system/client/types'; -import type { DictData } from '@/api/system/dict/data/types'; import { addClient, changeStatus, delClient, getClient, listClient, updateClient } from '@/api/system/client'; import DictTag from '@/components/common/DictTag'; import EllipsisText from '@/components/common/EllipsisText'; @@ -24,14 +23,13 @@ import { useDict } from '@/hooks/useDict'; import { useTableExport } from '@/hooks/useTableExport'; import { useTableSelection } from '@/hooks/useTableSelection'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; +import { confirmAction } from '@/utils/modal'; import { hasPermi } from '@/utils/permission'; import { toPageQuery, toTableData } from '@/utils/ruoyi'; const defaultClientForm: ClientForm = { status: '0' }; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} function getRuleList(ruleList?: string[], ruleValue?: string) { if (Array.isArray(ruleList) && ruleList.length) return ruleList; @@ -42,17 +40,6 @@ function getRuleList(ruleList?: string[], ruleValue?: string) { .filter(Boolean); } -function confirmAction(content: string) { - return new Promise((resolve, reject) => { - Modal.confirm({ - title: '系统提示', - content, - onOk: () => resolve(), - onCancel: () => reject(new Error('cancelled')) - }); - }); -} - export default function SystemClientPage() { const actionRef = useRef(undefined); const [form] = Form.useForm(); diff --git a/src/pages/system/config/index.tsx b/src/pages/system/config/index.tsx index b7074689..b78c0d28 100644 --- a/src/pages/system/config/index.tsx +++ b/src/pages/system/config/index.tsx @@ -13,21 +13,19 @@ import { useBoolean } from 'ahooks'; import { Button, Form, message, Popconfirm } from 'antd'; import { useRef, useState } from 'react'; import type { ConfigForm, ConfigQuery, ConfigVO } from '@/api/system/config/types'; -import type { DictData } from '@/api/system/dict/data/types'; import { addConfig, delConfig, getConfig, listConfig, refreshConfigCache, updateConfig } from '@/api/system/config'; import DictTag from '@/components/common/DictTag'; import EllipsisText from '@/components/common/EllipsisText'; import RowActions from '@/components/common/RowActions'; +import { useDateRangeQuery } from '@/hooks/useDateRangeQuery'; import { useDict } from '@/hooks/useDict'; import { useTableExport } from '@/hooks/useTableExport'; import { useTableSelection } from '@/hooks/useTableSelection'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; import { hasPermi } from '@/utils/permission'; -import { addDateRange, formatDateTimeRange, toPageQuery, toTableData } from '@/utils/ruoyi'; +import { toPageQuery, toTableData } from '@/utils/ruoyi'; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} export default function SystemConfigPage() { const actionRef = useRef(undefined); @@ -38,6 +36,7 @@ export default function SystemConfigPage() { const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false); const [modalTitle, setModalTitle] = useState(''); const { updateExportParams, exportFile } = useTableExport(); + const { applyDateRange: applyCreateTimeDateRange } = useDateRangeQuery(); const canAdd = hasPermi(userInfo, ['system:config:add']); const canEdit = hasPermi(userInfo, ['system:config:edit']); const canRemove = hasPermi(userInfo, ['system:config:remove']); @@ -132,7 +131,7 @@ export default function SystemConfigPage() { rowSelection={{ selectedRowKeys: ids, onChange: handleSelectionChange }} request={async params => { const { createTimeRange, ...tableParams } = params; - const query = addDateRange(toPageQuery(tableParams), formatDateTimeRange(createTimeRange)); + const query = applyCreateTimeDateRange(toPageQuery(tableParams), createTimeRange); updateExportParams(query); const res = await listConfig(query); return toTableData(res); diff --git a/src/pages/system/dept/index.tsx b/src/pages/system/dept/index.tsx index ba959644..97cf1868 100644 --- a/src/pages/system/dept/index.tsx +++ b/src/pages/system/dept/index.tsx @@ -13,16 +13,17 @@ import { } from '@ant-design/pro-components'; import { useBoolean } from 'ahooks'; import { Button, Form, message, Tag } from 'antd'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useMemo, useRef, useState } from 'react'; import type { DeptForm, DeptQuery, DeptVO } from '@/api/system/dept/types'; -import type { DictData } from '@/api/system/dict/data/types'; import type { UserVO } from '@/api/system/user/types'; import { addDept, delDept, getDept, listDept, listDeptExcludeChild, updateDept } from '@/api/system/dept'; import { listUserByDeptId } from '@/api/system/user'; import EllipsisText from '@/components/common/EllipsisText'; import RowActions from '@/components/common/RowActions'; import { useDict } from '@/hooks/useDict'; +import { useTreeTableExpand } from '@/hooks/useTreeTableExpand'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; import { hasPermi } from '@/utils/permission'; import { handleTree } from '@/utils/ruoyi'; @@ -37,12 +38,6 @@ interface TreeSelectNode { children?: TreeSelectNode[]; } -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ - label: item.dictLabel, - value: item.dictValue - })); -} function toTreeSelectData(depts: DeptVO[]): TreeSelectNode[] { return depts.map(dept => ({ @@ -52,16 +47,6 @@ function toTreeSelectData(depts: DeptVO[]): TreeSelectNode[] { })); } -function collectDeptKeys(depts: DeptVO[]): Array { - return depts.reduce>((keys, dept) => { - keys.push(dept.deptId); - if (dept.children?.length) { - keys.push(...collectDeptKeys(dept.children)); - } - return keys; - }, []); -} - export default function SystemDeptPage() { const actionRef = useRef(undefined); const [form] = Form.useForm(); @@ -71,8 +56,11 @@ export default function SystemDeptPage() { const [modalTitle, setModalTitle] = useState(''); const [deptOptions, setDeptOptions] = useState([]); const [deptUserList, setDeptUserList] = useState([]); - const [expandedRowKeys, setExpandedRowKeys] = useState([]); const [lastDeptList, setLastDeptList] = useState([]); + const { expandedRowKeys, onExpandedRowsChange, syncExpandedRows, toggleExpandAll } = useTreeTableExpand( + dept => dept.deptId, + { initialExpandAll: true } + ); const canAdd = hasPermi(userInfo, ['system:dept:add']); const canEdit = hasPermi(userInfo, ['system:dept:edit']); @@ -80,10 +68,6 @@ export default function SystemDeptPage() { const parentId = Form.useWatch('parentId', form); const deptTreeSelectData = useMemo(() => toTreeSelectData(deptOptions), [deptOptions]); - useEffect(() => { - setExpandedRowKeys(collectDeptKeys(lastDeptList)); - }, [lastDeptList]); - const loadDeptOptions = async (excludeDeptId?: string | number) => { const res = excludeDeptId ? await listDeptExcludeChild(excludeDeptId) : await listDept(); const tree = handleTree(res.data || [], 'deptId'); @@ -212,12 +196,13 @@ export default function SystemDeptPage() { pagination={false} expandable={{ expandedRowKeys, - onExpandedRowsChange: setExpandedRowKeys + onExpandedRowsChange }} request={async params => { const res = await listDept(params); const data = handleTree(res.data || [], 'deptId'); setLastDeptList(data); + syncExpandedRows(data); return { data, success: true }; }} toolbar={{ title: '部门列表' }} @@ -229,7 +214,7 @@ export default function SystemDeptPage() { ), diff --git a/src/pages/system/menu/index.tsx b/src/pages/system/menu/index.tsx index 9bbae5ee..83d4746c 100644 --- a/src/pages/system/menu/index.tsx +++ b/src/pages/system/menu/index.tsx @@ -14,7 +14,6 @@ import { import { useBoolean } from 'ahooks'; import { Button, Form, message, Modal, Space, Tag, Tree } from 'antd'; import { useMemo, useRef, useState } from 'react'; -import type { DictData } from '@/api/system/dict/data/types'; import type { MenuForm, MenuQuery, MenuTreeOption, MenuVO } from '@/api/system/menu/types'; import { addMenu, cascadeDelMenu, delMenu, getMenu, listMenu, treeselect, updateMenu } from '@/api/system/menu'; import DictTag from '@/components/common/DictTag'; @@ -22,8 +21,10 @@ import EllipsisText from '@/components/common/EllipsisText'; import IconSelect from '@/components/common/IconSelect'; import RowActions from '@/components/common/RowActions'; import { useDict } from '@/hooks/useDict'; +import { useLoading } from '@/hooks/useLoading'; import { useUserStore } from '@/stores/userStore'; import { routeIcon } from '@/utils/menu'; +import { dictOptions } from '@/utils/dict'; import { hasPermi } from '@/utils/permission'; import { handleTree } from '@/utils/ruoyi'; @@ -37,9 +38,6 @@ const defaultMenuForm: MenuForm = { status: '0' }; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} function menuTypeMeta(row: MenuVO) { if (row.menuType === 'F') return { label: '按钮', color: 'orange' }; @@ -66,7 +64,7 @@ export default function SystemMenuPage() { const [modalTitle, setModalTitle] = useState(''); const [cascadeOpen, { setTrue: openCascadeModal, setFalse: closeCascadeModal }] = useBoolean(false); const [cascadeKeys, setCascadeKeys] = useState>([]); - const [cascadeLoading, setCascadeLoading] = useState(false); + const { loading: cascadeLoading, withLoading: withCascadeLoading } = useLoading(); const canAdd = hasPermi(userInfo, ['system:menu:add']); const canEdit = hasPermi(userInfo, ['system:menu:edit']); @@ -122,15 +120,12 @@ export default function SystemMenuPage() { message.warning('请选择要删除的菜单'); return; } - setCascadeLoading(true); - try { + await withCascadeLoading(async () => { await cascadeDelMenu(cascadeKeys); message.success('删除成功'); closeCascadeModal(); actionRef.current?.reload(); - } finally { - setCascadeLoading(false); - } + }); }; const columns: ProColumns[] = [ diff --git a/src/pages/system/notice/index.tsx b/src/pages/system/notice/index.tsx index 4b63d9bc..e33f5b79 100644 --- a/src/pages/system/notice/index.tsx +++ b/src/pages/system/notice/index.tsx @@ -14,7 +14,6 @@ import { history, useLocation } from '@umijs/max'; import { useBoolean } from 'ahooks'; import { Button, Descriptions, Form, message, Modal, Popconfirm } from 'antd'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import type { DictData } from '@/api/system/dict/data/types'; import type { NoticeForm, NoticeQuery, NoticeVO } from '@/api/system/notice/types'; import { addNotice, delNotice, getNotice, listNotice, updateNotice } from '@/api/system/notice'; import DictTag from '@/components/common/DictTag'; @@ -25,6 +24,7 @@ import { useDict } from '@/hooks/useDict'; import { useTableSelection } from '@/hooks/useTableSelection'; import { useUserStore } from '@/stores/userStore'; import { resolveOssContent } from '@/utils/ossContent'; +import { dictOptions } from '@/utils/dict'; import { hasPermi } from '@/utils/permission'; import { toPageQuery, toTableData } from '@/utils/ruoyi'; import { sanitizeHtml } from '@/utils/sanitize'; @@ -32,9 +32,6 @@ import { sanitizeHtml } from '@/utils/sanitize'; const defaultNoticeForm: NoticeForm = { status: '0' }; const emptyNoticeContent = '

暂无公告内容

'; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} function SafeHtmlContent({ html }: { html: string }) { const ref = useRef(null); diff --git a/src/pages/system/oss/config.tsx b/src/pages/system/oss/config.tsx index 1d46899c..c801cd33 100644 --- a/src/pages/system/oss/config.tsx +++ b/src/pages/system/oss/config.tsx @@ -10,9 +10,8 @@ import { type ProColumns } from '@ant-design/pro-components'; import { useBoolean } from 'ahooks'; -import { Button, Form, message, Modal, Popconfirm, Switch, Tag } from 'antd'; +import { Button, Form, message, Popconfirm, Switch, Tag } from 'antd'; import { useMemo, useRef, useState } from 'react'; -import type { DictData } from '@/api/system/dict/data/types'; import type { OssConfigForm, OssConfigQuery, OssConfigVO } from '@/api/system/ossConfig/types'; import { addOssConfig, @@ -27,6 +26,8 @@ import RowActions from '@/components/common/RowActions'; import { useDict } from '@/hooks/useDict'; import { useTableSelection } from '@/hooks/useTableSelection'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; +import { confirmAction } from '@/utils/modal'; import { hasPermi } from '@/utils/permission'; import { toPageQuery, toTableData } from '@/utils/ruoyi'; @@ -36,21 +37,6 @@ const defaultOssConfigForm: OssConfigForm = { status: 'N' }; -function confirmAction(content: string) { - return new Promise((resolve, reject) => { - Modal.confirm({ - title: '系统提示', - content, - onOk: () => resolve(), - onCancel: () => reject(new Error('cancelled')) - }); - }); -} - -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} - function accessPolicyTag(value?: string) { if (value === '0') return private; if (value === '1') return public; diff --git a/src/pages/system/oss/index.tsx b/src/pages/system/oss/index.tsx index c88f875e..d2232249 100644 --- a/src/pages/system/oss/index.tsx +++ b/src/pages/system/oss/index.tsx @@ -19,10 +19,11 @@ import FileUpload from '@/components/common/FileUpload'; import ImagePreview from '@/components/common/ImagePreview'; import ImageUpload from '@/components/common/ImageUpload'; import RowActions from '@/components/common/RowActions'; +import { useDateRangeQuery } from '@/hooks/useDateRangeQuery'; import { useUserStore } from '@/stores/userStore'; import { saveValidatedBlob } from '@/utils/download'; import { hasPermi } from '@/utils/permission'; -import { addDateRange, formatDateTimeRange, toPageQuery, toTableData, withTableSort } from '@/utils/ruoyi'; +import { toPageQuery, toTableData, withTableSort } from '@/utils/ruoyi'; function isImage(fileSuffix?: string) { return ['.png', '.jpg', '.jpeg'].includes((fileSuffix || '').toLowerCase()); @@ -37,6 +38,7 @@ export default function SystemOssPage() { const [uploadTitle, setUploadTitle] = useState('上传文件'); const [uploadType, setUploadType] = useState<'file' | 'image'>('file'); const [uploadValue, setUploadValue] = useState(''); + const { applyDateRange: applyCreateTimeDateRange } = useDateRangeQuery(); const canUpload = hasPermi(userInfo, ['system:oss:upload']); const canRemove = hasPermi(userInfo, ['system:oss:remove']); @@ -149,9 +151,9 @@ export default function SystemOssPage() { rowSelection={{ selectedRowKeys: ids, onChange: (_, rows) => setSelectedRows(rows) }} request={async (params, sort) => { const { createTimeRange, ...tableParams } = params; - const query = addDateRange( + const query = applyCreateTimeDateRange( withTableSort(toPageQuery(tableParams), sort, { orderByColumn: 'createTime', isAsc: 'ascending' }), - formatDateTimeRange(createTimeRange) + createTimeRange ); const preview = await getConfigKey('sys.oss.previewListResource'); setPreviewListResource(preview.data === undefined ? true : preview.data === 'true'); diff --git a/src/pages/system/post/index.tsx b/src/pages/system/post/index.tsx index 328f88fa..accb9bb1 100644 --- a/src/pages/system/post/index.tsx +++ b/src/pages/system/post/index.tsx @@ -15,7 +15,6 @@ import { useBoolean } from 'ahooks'; import { Button, Form, message, Popconfirm, Tag } from 'antd'; import { useEffect, useMemo, useRef, useState } from 'react'; import type { DeptTreeVO } from '@/api/system/dept/types'; -import type { DictData } from '@/api/system/dict/data/types'; import type { PostForm, PostQuery, PostVO } from '@/api/system/post/types'; import { addPost, delPost, getPost, listPost, postDeptTreeSelect, updatePost } from '@/api/system/post'; import EllipsisText from '@/components/common/EllipsisText'; @@ -25,14 +24,12 @@ import { useDict } from '@/hooks/useDict'; import { useTableExport } from '@/hooks/useTableExport'; import { useTableSelection } from '@/hooks/useTableSelection'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; import { hasPermi } from '@/utils/permission'; import { toPageQuery, toTableData } from '@/utils/ruoyi'; const defaultPostForm: PostForm = { postSort: 0, status: '0' }; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} interface TreeSelectNode { title: string; diff --git a/src/pages/system/role/index.tsx b/src/pages/system/role/index.tsx index 61f7f4fe..3121bc90 100644 --- a/src/pages/system/role/index.tsx +++ b/src/pages/system/role/index.tsx @@ -19,19 +19,21 @@ import { } from '@ant-design/pro-components'; import { history } from '@umijs/max'; import { useBoolean } from 'ahooks'; -import { Button, Form, message, Modal, Popconfirm, Switch } from 'antd'; +import { Button, Form, message, Popconfirm, Switch } from 'antd'; import { useRef, useState } from 'react'; -import type { DictData } from '@/api/system/dict/data/types'; import type { RoleForm, RoleQuery, RoleVO } from '@/api/system/role/types'; import { roleMenuTreeselect } from '@/api/system/menu'; import { addRole, changeRoleStatus, delRole, getRole, listRole, updateRole } from '@/api/system/role'; import RowActions from '@/components/common/RowActions'; +import { useDateRangeQuery } from '@/hooks/useDateRangeQuery'; import { useDict } from '@/hooks/useDict'; import { useTableExport } from '@/hooks/useTableExport'; import { useTableSelection } from '@/hooks/useTableSelection'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; +import { confirmAction } from '@/utils/modal'; import { hasPermi } from '@/utils/permission'; -import { addDateRange, formatDateTimeRange, toPageQuery, toTableData } from '@/utils/ruoyi'; +import { toPageQuery, toTableData } from '@/utils/ruoyi'; import RolePermissionModal from './components/RolePermissionModal'; const defaultRoleForm: RoleForm = { @@ -44,24 +46,6 @@ const defaultRoleForm: RoleForm = { deptIds: [] }; -function confirmAction(content: string) { - return new Promise((resolve, reject) => { - Modal.confirm({ - title: '系统提示', - content, - onOk: () => resolve(), - onCancel: () => reject(new Error('cancelled')) - }); - }); -} - -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ - label: item.dictLabel, - value: item.dictValue - })); -} - export default function SystemRolePage() { const actionRef = useRef(undefined); const [form] = Form.useForm(); @@ -78,6 +62,7 @@ export default function SystemRolePage() { const [permissionOpen, { setTrue: openPermissionModal, setFalse: closePermissionModal }] = useBoolean(false); const [permissionRoleId, setPermissionRoleId] = useState(); const { updateExportParams, exportFile } = useTableExport(); + const { applyDateRange: applyCreateTimeDateRange } = useDateRangeQuery(); const canAdd = hasPermi(userInfo, ['system:role:add']); const canEdit = hasPermi(userInfo, ['system:role:edit']); @@ -257,7 +242,7 @@ export default function SystemRolePage() { }} request={async params => { const { createTimeRange, ...tableParams } = params; - const query = addDateRange(toPageQuery(tableParams), formatDateTimeRange(createTimeRange)); + const query = applyCreateTimeDateRange(toPageQuery(tableParams), createTimeRange); updateExportParams(query); const res = await listRole(query); return toTableData(res); diff --git a/src/pages/system/user/index.tsx b/src/pages/system/user/index.tsx index 422062d6..dd34ea99 100644 --- a/src/pages/system/user/index.tsx +++ b/src/pages/system/user/index.tsx @@ -32,12 +32,15 @@ import { } from '@/api/system/user'; import RowActions from '@/components/common/RowActions'; import TreePanel from '@/components/common/TreePanel'; +import { useDateRangeQuery } from '@/hooks/useDateRangeQuery'; import { useDict } from '@/hooks/useDict'; import { useTableExport } from '@/hooks/useTableExport'; import { useTableSelection } from '@/hooks/useTableSelection'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; +import { confirmAction } from '@/utils/modal'; import { hasPermi } from '@/utils/permission'; -import { addDateRange, formatDateTimeRange, toPageQuery, toTableData } from '@/utils/ruoyi'; +import { filterTree, toPageQuery, toTableData } from '@/utils/ruoyi'; import UserDetailDrawer from './components/UserDetailDrawer'; import UserFormModal from './components/UserFormModal'; import UserImportModal from './components/UserImportModal'; @@ -55,17 +58,6 @@ interface TreeSelectNode { children?: TreeSelectNode[]; } -function filterDisabledDept(depts: DeptTreeVO[]): DeptTreeVO[] { - return depts.reduce((result, dept) => { - if (dept.disabled) return result; - result.push({ - ...dept, - children: dept.children?.length ? filterDisabledDept(dept.children) : [] - }); - return result; - }, []); -} - function toTreeSelectData(depts: DeptTreeVO[]): TreeSelectNode[] { return depts.map(dept => ({ title: dept.label, @@ -75,23 +67,6 @@ function toTreeSelectData(depts: DeptTreeVO[]): TreeSelectNode[] { })); } -function dictOptions(dicts?: Array<{ dictLabel: string; dictValue: string }>) { - return (dicts || []).map(item => ({ - label: item.dictLabel, - value: item.dictValue - })); -} - -function confirmAction(content: string) { - return new Promise((resolve, reject) => { - Modal.confirm({ - title: '系统提示', - content, - onOk: () => resolve(), - onCancel: () => reject(new Error('cancelled')) - }); - }); -} export default function SystemUserPage() { const actionRef = useRef(undefined); @@ -116,6 +91,7 @@ export default function SystemUserPage() { const [viewOpen, { setTrue: openViewDrawer, setFalse: closeViewDrawer }] = useBoolean(false); const [viewUserId, setViewUserId] = useState(); const { updateExportParams, exportFile } = useTableExport(); + const { applyDateRange: applyCreateTimeDateRange } = useDateRangeQuery(); const canAdd = hasPermi(userInfo, ['system:user:add']); const canEdit = hasPermi(userInfo, ['system:user:edit']); @@ -127,7 +103,7 @@ export default function SystemUserPage() { useEffect(() => { Promise.all([deptTreeSelect(), getConfigKey('sys.user.initPassword')]).then(([deptRes, configRes]) => { setDeptOptions(deptRes.data || []); - setEnabledDeptOptions(filterDisabledDept(deptRes.data || [])); + setEnabledDeptOptions(filterTree(deptRes.data || [], dept => !dept.disabled)); setInitPassword(configRes.data || ''); }); }, []); @@ -385,12 +361,12 @@ export default function SystemUserPage() { }} request={async params => { const { createTimeRange, ...tableParams } = params; - const query = addDateRange( + const query = applyCreateTimeDateRange( { ...toPageQuery(tableParams), deptId }, - formatDateTimeRange(createTimeRange) + createTimeRange ); updateExportParams(query); const res = await listUser(query); diff --git a/src/pages/system/user/profile/index.tsx b/src/pages/system/user/profile/index.tsx index 4e818f1b..232865e8 100644 --- a/src/pages/system/user/profile/index.tsx +++ b/src/pages/system/user/profile/index.tsx @@ -21,7 +21,6 @@ import { import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import Cropper, { type ReactCropperElement } from 'react-cropper'; import type { OnlineVO } from '@/api/monitor/online/types'; -import type { DictData } from '@/api/system/dict/data/types'; import type { SocialAuthVO } from '@/api/system/social/types'; import type { UserForm, UserInfoVO } from '@/api/system/user/types'; import { delOnline, getOnline } from '@/api/monitor/online'; @@ -35,6 +34,8 @@ import wechatIcon from '@/assets/icons/svg/wechat.svg'; import defaultAvatar from '@/assets/images/profile.jpg'; import DictTag from '@/components/common/DictTag'; import { useDict } from '@/hooks/useDict'; +import { useLoading } from '@/hooks/useLoading'; +import { dictOptions } from '@/utils/dict'; import { usePermissionStore } from '@/stores/permissionStore'; import { getUploadErrorMessage, validateUploadFile } from '@/utils/upload'; @@ -70,9 +71,6 @@ function socialProviderNode(source?: string) { ); } -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} function normalizeOnlineDevices(payload: OnlineVO[] | { rows?: OnlineVO[] } | undefined) { if (Array.isArray(payload)) return payload; @@ -87,8 +85,8 @@ export default function Profile() { const [profile, setProfile] = useState(); const [devices, setDevices] = useState([]); const [auths, setAuths] = useState([]); - const [loading, setLoading] = useState(false); - const [avatarUploading, setAvatarUploading] = useState(false); + const { loading, withLoading } = useLoading(); + const { loading: avatarUploading, withLoading: withAvatarUploading } = useLoading(); const [avatarCropOpen, setAvatarCropOpen] = useState(false); const [avatarCropUrl, setAvatarCropUrl] = useState(''); const [avatarFileName, setAvatarFileName] = useState('avatar.png'); @@ -97,17 +95,14 @@ export default function Profile() { const genderOptions = useMemo(() => dictOptions(dicts.sys_user_gender), [dicts.sys_user_gender]); const loadProfile = useCallback(async () => { - setLoading(true); - try { + await withLoading(async () => { const [profileRes, authRes, onlineRes] = await Promise.all([getUserProfile(), getAuthList(), getOnline()]); setProfile(profileRes.data); setAuths(authRes.data || []); setDevices(normalizeOnlineDevices(onlineRes.data)); userForm.setFieldsValue(profileRes.data.user || {}); - } finally { - setLoading(false); - } - }, [userForm]); + }); + }, [userForm, withLoading]); useEffect(() => { loadProfile(); @@ -169,18 +164,17 @@ export default function Profile() { } const formData = new FormData(); formData.append('avatarfile', blob, avatarFileName); - setAvatarUploading(true); - try { - await uploadAvatar(formData); - message.success('修改成功'); - closeAvatarCrop(); - await reloadMenus(); - await loadProfile(); - } catch (error) { - message.error(getUploadErrorMessage(error, '头像上传失败')); - } finally { - setAvatarUploading(false); - } + await withAvatarUploading(async () => { + try { + await uploadAvatar(formData); + message.success('修改成功'); + closeAvatarCrop(); + await reloadMenus(); + await loadProfile(); + } catch (error) { + message.error(getUploadErrorMessage(error, '头像上传失败')); + } + }); }; const deleteDevice = async (row: OnlineVO) => { diff --git a/src/pages/tool/gen/index.tsx b/src/pages/tool/gen/index.tsx index 85f804ea..323bcb0c 100644 --- a/src/pages/tool/gen/index.tsx +++ b/src/pages/tool/gen/index.tsx @@ -17,10 +17,11 @@ import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; import type { TableQuery, TableVO } from '@/api/tool/gen/types'; import { batchGenCode, delTable, getDataNames, listTable, previewTable, synchDb } from '@/api/tool/gen'; import EllipsisText from '@/components/common/EllipsisText'; +import { useDateRangeQuery } from '@/hooks/useDateRangeQuery'; import { useUserStore } from '@/stores/userStore'; import { saveValidatedBlob } from '@/utils/download'; import { hasPermi } from '@/utils/permission'; -import { addDateRange, toPageQuery, toTableData } from '@/utils/ruoyi'; +import { toPageQuery, toTableData } from '@/utils/ruoyi'; import ImportTableModal from './components/ImportTableModal'; function previewName(path: string) { @@ -63,6 +64,7 @@ export default function ToolGenPage() { const [currentPage, setCurrentPage] = useState( () => Number(new URLSearchParams(location.search).get('pageNum')) || 1 ); + const { applyDateRange } = useDateRangeQuery(); const canCode = hasPermi(userInfo, ['tool:gen:code']); const canImport = hasPermi(userInfo, ['tool:gen:import']); @@ -238,7 +240,7 @@ export default function ToolGenPage() { const { dateRange, ...tableParams } = params; setCurrentPage(tableParams.current || 1); setCurrentDataName(tableParams.dataName || undefined); - const query = addDateRange(toPageQuery(tableParams), dateRange); + const query = applyDateRange(toPageQuery(tableParams), dateRange); const res = await listTable(query); return toTableData(res); }} diff --git a/src/pages/workflow/category/index.tsx b/src/pages/workflow/category/index.tsx index 131013f5..58ff358d 100644 --- a/src/pages/workflow/category/index.tsx +++ b/src/pages/workflow/category/index.tsx @@ -1,4 +1,3 @@ -import type { Key } from 'react'; import { DeleteOutlined, EditOutlined, PlusOutlined, SortAscendingOutlined } from '@ant-design/icons'; import { ModalForm, @@ -16,6 +15,7 @@ import { useMemo, useRef, useState } from 'react'; import type { CategoryForm, CategoryQuery, CategoryVO } from '@/api/workflow/category/types'; import { addCategory, delCategory, getCategory, listCategory, updateCategory } from '@/api/workflow/category'; import RowActions from '@/components/common/RowActions'; +import { useTreeTableExpand } from '@/hooks/useTreeTableExpand'; import { useUserStore } from '@/stores/userStore'; import { hasPermi } from '@/utils/permission'; import { handleTree } from '@/utils/ruoyi'; @@ -36,18 +36,14 @@ function toTreeSelectData(nodes: CategoryVO[]): CategorySelectNode[] { })); } -function collectCategoryKeys(nodes: CategoryVO[]): Key[] { - return nodes.flatMap(node => [node.categoryId, ...(node.children ? collectCategoryKeys(node.children) : [])]); -} - export default function WorkflowCategoryPage() { const actionRef = useRef(undefined); const [form] = Form.useForm(); const userInfo = useUserStore(state => state.userInfo); const [categoryOptions, setCategoryOptions] = useState([]); const [tableRows, setTableRows] = useState([]); - const [expandAll, setExpandAll] = useState(false); - const [expandedRowKeys, setExpandedRowKeys] = useState([]); + const { expandAll, expandedRowKeys, onExpandedRowsChange, syncExpandedRows, toggleExpandAll } = + useTreeTableExpand(row => row.categoryId); const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false); const [modalTitle, setModalTitle] = useState(''); @@ -131,13 +127,13 @@ export default function WorkflowCategoryPage() { search={{ labelWidth: 90 }} expandable={{ expandedRowKeys, - onExpandedRowsChange: keys => setExpandedRowKeys([...keys]) + onExpandedRowsChange }} request={async params => { const res = await listCategory({ categoryName: params.categoryName }); const rows = handleTree(res.data || [], 'categoryId', 'parentId'); setTableRows(rows); - setExpandedRowKeys(expandAll ? collectCategoryKeys(rows) : []); + syncExpandedRows(rows, expandAll); return { data: rows, total: rows.length, success: true }; }} toolbar={{ title: '流程分类列表' }} @@ -150,13 +146,7 @@ export default function WorkflowCategoryPage() { diff --git a/src/pages/workflow/leave/index.tsx b/src/pages/workflow/leave/index.tsx index ea706cda..d1bcdfad 100644 --- a/src/pages/workflow/leave/index.tsx +++ b/src/pages/workflow/leave/index.tsx @@ -10,7 +10,6 @@ import { PageContainer, ProTable, type ActionType, type ProColumns } from '@ant- import { history } from '@umijs/max'; import { Button, message, Tag } from 'antd'; import { useMemo, useRef } from 'react'; -import type { DictData } from '@/api/system/dict/data/types'; import type { LeaveQuery, LeaveVO } from '@/api/workflow/leave/types'; import { cancelProcessApply } from '@/api/workflow/instance'; import { delLeave, listLeave } from '@/api/workflow/leave'; @@ -21,6 +20,7 @@ import { useDict } from '@/hooks/useDict'; import { useTableExport } from '@/hooks/useTableExport'; import { useTableSelection } from '@/hooks/useTableSelection'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; import { hasPermi } from '@/utils/permission'; import { toPageQuery, toTableData } from '@/utils/ruoyi'; @@ -31,9 +31,6 @@ const leaveTypeOptions = [ { value: '4', label: '婚假' } ]; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} function leaveTypeLabel(value?: string) { return leaveTypeOptions.find(item => item.value === value)?.label || value || '-'; diff --git a/src/pages/workflow/processDefinition/index.tsx b/src/pages/workflow/processDefinition/index.tsx index 822a8414..c00d3f23 100644 --- a/src/pages/workflow/processDefinition/index.tsx +++ b/src/pages/workflow/processDefinition/index.tsx @@ -11,7 +11,7 @@ import { import { PageContainer, ProTable, type ActionType, type ProColumns } from '@ant-design/pro-components'; import { history, useLocation } from '@umijs/max'; import { useBoolean } from 'ahooks'; -import { Button, Form, message, Modal, Popconfirm, Space, Switch, Tabs, Tag } from 'antd'; +import { Button, Form, message, Popconfirm, Space, Switch, Tabs, Tag } from 'antd'; import { useCallback, useEffect, useRef, useState } from 'react'; import type { PageResult, R } from '@/api/types'; import type { CategoryTreeVO } from '@/api/workflow/category/types'; @@ -31,8 +31,10 @@ import { } from '@/api/workflow/definition'; import EllipsisText from '@/components/common/EllipsisText'; import TreePanel from '@/components/common/TreePanel'; +import { useSearchReset } from '@/hooks/useSearchReset'; import { useUserStore } from '@/stores/userStore'; import { saveValidatedBlob } from '@/utils/download'; +import { confirmAction } from '@/utils/modal'; import { hasPermi } from '@/utils/permission'; import { toPageQuery, toTableData } from '@/utils/ruoyi'; import DefinitionFormModal from './components/DefinitionFormModal'; @@ -59,17 +61,6 @@ const defaultForm: FlowDefinitionForm = { modelValue: 'CLASSICS' }; -function confirmAction(content: string) { - return new Promise((resolve, reject) => { - Modal.confirm({ - title: '系统提示', - content, - onOk: () => resolve(), - onCancel: () => reject(new Error('cancelled')) - }); - }); -} - function requestDefinitionList( tab: DefinitionTab, query: FlowDefinitionQuery @@ -104,6 +95,10 @@ export default function WorkflowProcessDefinitionPage() { const selectedIds = selectedRows.map(item => item.id).filter(Boolean); const selectedOne = selectedRows.length === 1 ? selectedRows[0] : undefined; + const resetSearch = useSearchReset( + actionRef, + useCallback(() => setCategory(undefined), []) + ); const switchTab = useCallback((nextTab: DefinitionTab) => { setSelectedRows([]); @@ -224,11 +219,6 @@ export default function WorkflowProcessDefinitionPage() { await saveValidatedBlob(blob, `${selectedOne.flowCode}.json`); }; - const resetSearch = () => { - setCategory(undefined); - setTimeout(() => actionRef.current?.reloadAndRest?.(), 0); - }; - const columns: ProColumns[] = [ { title: '流程定义名称', diff --git a/src/pages/workflow/processInstance/index.tsx b/src/pages/workflow/processInstance/index.tsx index 490cbf80..8f5d8179 100644 --- a/src/pages/workflow/processInstance/index.tsx +++ b/src/pages/workflow/processInstance/index.tsx @@ -9,8 +9,7 @@ import { } from '@ant-design/pro-components'; import { useBoolean } from 'ahooks'; import { Badge, Button, Card, Form, Input, message, Modal, Popconfirm, Tabs, Tag } from 'antd'; -import { useEffect, useMemo, useRef, useState } from 'react'; -import type { DictData } from '@/api/system/dict/data/types'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { UserVO } from '@/api/system/user/types'; import type { PageResult, R } from '@/api/types'; import type { CategoryTreeVO } from '@/api/workflow/category/types'; @@ -33,15 +32,16 @@ import RowActions from '@/components/common/RowActions'; import TreePanel from '@/components/common/TreePanel'; import UserSelect from '@/components/common/UserSelect'; import { useDict } from '@/hooks/useDict'; +import { useLoading } from '@/hooks/useLoading'; +import { useSearchReset } from '@/hooks/useSearchReset'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; +import { confirmAction } from '@/utils/modal'; import { hasPermi } from '@/utils/permission'; import { toPageQuery, toTableData } from '@/utils/ruoyi'; type InstanceTab = 'running' | 'finish'; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} function requestInstanceList(tab: InstanceTab, query: FlowInstanceQuery): Promise>> { if (tab === 'running') return pageByRunning(query); @@ -58,17 +58,6 @@ function openBusinessForm(row: FlowInstanceVO) { }); } -function confirmAction(content: string) { - return new Promise((resolve, reject) => { - Modal.confirm({ - title: '系统提示', - content, - onOk: () => resolve(), - onCancel: () => reject(new Error('cancelled')) - }); - }); -} - export default function WorkflowProcessInstancePage() { const actionRef = useRef(undefined); const [invalidForm] = Form.useForm<{ comment?: string }>(); @@ -84,7 +73,7 @@ export default function WorkflowProcessInstancePage() { const [invalidOpen, { setTrue: openInvalidModal, setFalse: closeInvalidModal }] = useBoolean(false); const [invalidRow, setInvalidRow] = useState(); const [variableOpen, { setTrue: openVariableModal, setFalse: closeVariableModal }] = useBoolean(false); - const [variableLoading, setVariableLoading] = useState(false); + const { loading: variableLoading, withLoading: withVariableLoading } = useLoading(); const [variableRow, setVariableRow] = useState(); const [variableText, setVariableText] = useState(''); @@ -97,6 +86,13 @@ export default function WorkflowProcessInstancePage() { const businessStatusOptions = useMemo(() => dictOptions(dicts.wf_business_status), [dicts.wf_business_status]); const selectedIds = selectedRows.map(item => item.id).filter(Boolean); const selectedApplicantIds = selectedApplicants.map(item => item.userId).filter(Boolean) as Array; + const resetSearch = useSearchReset( + actionRef, + useCallback(() => { + setCategory(undefined); + setSelectedApplicants([]); + }, []) + ); useEffect(() => { categoryTree().then(res => setCategoryOptions(res.data || [])); @@ -123,12 +119,6 @@ export default function WorkflowProcessInstancePage() { actionRef.current?.reloadAndRest?.(); }; - const resetSearch = () => { - setCategory(undefined); - setSelectedApplicants([]); - setTimeout(() => actionRef.current?.reloadAndRest?.(), 0); - }; - const handleDelete = async (row?: FlowInstanceVO) => { const ids = row?.id ? [row.id] : selectedIds; if (!ids.length) return; @@ -160,14 +150,11 @@ export default function WorkflowProcessInstancePage() { const openVariable = async (row: FlowInstanceVO) => { setVariableRow(row); openVariableModal(); - setVariableLoading(true); variableForm.resetFields(); - try { + await withVariableLoading(async () => { const res = await instanceVariable(row.id); setVariableText(res.data.variable || ''); - } finally { - setVariableLoading(false); - } + }); }; const submitVariable = async () => { diff --git a/src/pages/workflow/spel/index.tsx b/src/pages/workflow/spel/index.tsx index 88007180..bba03220 100644 --- a/src/pages/workflow/spel/index.tsx +++ b/src/pages/workflow/spel/index.tsx @@ -12,7 +12,6 @@ import { import { useBoolean } from 'ahooks'; import { Button, Form, message, Popconfirm } from 'antd'; import { useEffect, useMemo, useRef, useState } from 'react'; -import type { DictData } from '@/api/system/dict/data/types'; import type { SpelForm, SpelQuery, SpelVO } from '@/api/workflow/spel/types'; import { addSpel, delSpel, getSpel, listSpel, updateSpel } from '@/api/workflow/spel'; import DictTag from '@/components/common/DictTag'; @@ -21,15 +20,13 @@ import RowActions from '@/components/common/RowActions'; import { useDict } from '@/hooks/useDict'; import { useTableSelection } from '@/hooks/useTableSelection'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; import { hasPermi } from '@/utils/permission'; import { toPageQuery, toTableData } from '@/utils/ruoyi'; const defaultSpelForm: SpelForm = { status: '0' }; const spelPlaceholder = '例如:#{@组件名.方法名(#方法参数)} 或 ${方法参数}'; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} function buildViewSpel(values: Pick) { const comp = (values.componentName || '').trim(); diff --git a/src/pages/workflow/task/allTaskWaiting.tsx b/src/pages/workflow/task/allTaskWaiting.tsx index 29b0d918..274deb90 100644 --- a/src/pages/workflow/task/allTaskWaiting.tsx +++ b/src/pages/workflow/task/allTaskWaiting.tsx @@ -1,9 +1,8 @@ import { BellOutlined, EyeOutlined, SettingOutlined, SwapOutlined, UserAddOutlined } from '@ant-design/icons'; import { ModalForm, PageContainer, ProTable, type ActionType, type ProColumns } from '@ant-design/pro-components'; import { useBoolean } from 'ahooks'; -import { Badge, Button, Form, message, Modal, Tabs } from 'antd'; -import { useMemo, useRef, useState } from 'react'; -import type { DictData } from '@/api/system/dict/data/types'; +import { Badge, Button, Form, message, Tabs } from 'antd'; +import { useCallback, useMemo, useRef, useState } from 'react'; import type { UserVO } from '@/api/system/user/types'; import type { PageResult, R } from '@/api/types'; import type { FlowTaskVO, TaskQuery } from '@/api/workflow/task/types'; @@ -17,13 +16,13 @@ import MessageType from '@/components/workflow/MessageType'; import ProcessMeddle from '@/components/workflow/ProcessMeddle'; import UserNameDisplay from '@/components/workflow/UserNameDisplay'; import { useDict } from '@/hooks/useDict'; +import { useSearchReset } from '@/hooks/useSearchReset'; +import { dictOptions } from '@/utils/dict'; +import { confirmTitleSafe } from '@/utils/modal'; import { toPageQuery, toTableData } from '@/utils/ruoyi'; type AllTaskTab = 'waiting' | 'finish'; type UserSelectMode = 'applicant' | 'assignee'; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} function requestAllTaskList(tab: AllTaskTab, query: TaskQuery): Promise>> { if (tab === 'waiting') return pageByAllTaskWait(query); @@ -40,16 +39,6 @@ function openBusinessForm(row: FlowTaskVO) { }); } -function confirmAction(title: string) { - return new Promise((resolve, reject) => { - Modal.confirm({ - title, - onOk: () => resolve(), - onCancel: () => reject(new Error('cancelled')) - }); - }); -} - export default function WorkflowAllTaskWaitingPage() { const actionRef = useRef(undefined); const [urgeForm] = Form.useForm<{ message: string; messageType: string[] }>(); @@ -67,6 +56,10 @@ export default function WorkflowAllTaskWaitingPage() { const taskStatusOptions = useMemo(() => dictOptions(dicts.wf_task_status), [dicts.wf_task_status]); const selectedTaskIds = selectedRows.map(item => item.id).filter(Boolean); const selectedApplicantIds = selectedApplicants.map(item => item.userId).filter(Boolean) as Array; + const resetSearch = useSearchReset( + actionRef, + useCallback(() => setSelectedApplicants([]), []) + ); const changeTab = (key: string) => { setActiveTab(key as AllTaskTab); @@ -103,16 +96,12 @@ export default function WorkflowAllTaskWaitingPage() { return; } const userId = user.userId; - Modal.confirm({ - title: '是否确认提交?', - onOk: async () => { - await updateAssignee(selectedTaskIds, userId); - message.success('操作成功'); - closeUserModal(); - setSelectedRows([]); - actionRef.current?.reload(); - } - }); + if (!(await confirmTitleSafe('是否确认提交?'))) return; + await updateAssignee(selectedTaskIds, userId); + message.success('操作成功'); + closeUserModal(); + setSelectedRows([]); + actionRef.current?.reload(); return; } }; @@ -122,13 +111,8 @@ export default function WorkflowAllTaskWaitingPage() { actionRef.current?.reloadAndRest?.(); }; - const resetSearch = () => { - setSelectedApplicants([]); - setTimeout(() => actionRef.current?.reloadAndRest?.(), 0); - }; - const submitUrge = async (values: { message: string; messageType: string[] }) => { - await confirmAction('是否确认提交?'); + if (!(await confirmTitleSafe('是否确认提交?'))) return false; await urgeTask({ ...values, taskIdList: selectedTaskIds }); message.success('操作成功'); urgeForm.resetFields(); diff --git a/src/pages/workflow/task/myDocument.tsx b/src/pages/workflow/task/myDocument.tsx index 5482cc84..8e9cea86 100644 --- a/src/pages/workflow/task/myDocument.tsx +++ b/src/pages/workflow/task/myDocument.tsx @@ -2,7 +2,6 @@ import { DeleteOutlined, EditOutlined, EyeOutlined, RollbackOutlined } from '@an import { PageContainer, ProTable, type ActionType, type ProColumns } from '@ant-design/pro-components'; import { message, Tag } from 'antd'; import { useEffect, useMemo, useRef, useState } from 'react'; -import type { DictData } from '@/api/system/dict/data/types'; import type { CategoryTreeVO } from '@/api/workflow/category/types'; import type { FlowInstanceQuery, FlowInstanceVO } from '@/api/workflow/instance/types'; import { categoryTree } from '@/api/workflow/category'; @@ -14,12 +13,10 @@ import RowActions from '@/components/common/RowActions'; import TreePanel from '@/components/common/TreePanel'; import { useDict } from '@/hooks/useDict'; import { useUserStore } from '@/stores/userStore'; +import { dictOptions } from '@/utils/dict'; import { hasPermi } from '@/utils/permission'; import { toPageQuery, toTableData } from '@/utils/ruoyi'; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} function editableStatus(status?: string) { return status === 'draft' || status === 'cancel' || status === 'back'; diff --git a/src/pages/workflow/task/taskList.tsx b/src/pages/workflow/task/taskList.tsx index 4ea97878..208c87f6 100644 --- a/src/pages/workflow/task/taskList.tsx +++ b/src/pages/workflow/task/taskList.tsx @@ -3,7 +3,6 @@ import { PageContainer, ProTable, type ActionType, type ProColumns } from '@ant- import { useBoolean } from 'ahooks'; import { Badge, Button } from 'antd'; import { useMemo, useRef, useState } from 'react'; -import type { DictData } from '@/api/system/dict/data/types'; import type { UserVO } from '@/api/system/user/types'; import type { PageResult, R } from '@/api/types'; import type { FlowTaskVO, TaskQuery } from '@/api/workflow/task/types'; @@ -15,6 +14,7 @@ import RowActions from '@/components/common/RowActions'; import UserSelect from '@/components/common/UserSelect'; import UserNameDisplay from '@/components/workflow/UserNameDisplay'; import { useDict } from '@/hooks/useDict'; +import { dictOptions } from '@/utils/dict'; import { toPageQuery, toTableData } from '@/utils/ruoyi'; type TaskListType = 'wait' | 'finish' | 'copy'; @@ -29,9 +29,6 @@ const pageTitle: Record = { copy: '抄送任务' }; -function dictOptions(dicts?: DictData[]) { - return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue })); -} function requestTaskList(type: TaskListType, query: TaskQuery): Promise>> { if (type === 'wait') return pageByTaskWait(query); diff --git a/src/utils/dict.ts b/src/utils/dict.ts new file mode 100644 index 00000000..2d58bb58 --- /dev/null +++ b/src/utils/dict.ts @@ -0,0 +1,9 @@ + +import type { DictData } from '@/api/system/dict/data/types'; + +export function dictOptions(dicts?: Pick[]) { + return (dicts || []).map(item => ({ + label: item.dictLabel, + value: item.dictValue + })); +} diff --git a/src/utils/modal.ts b/src/utils/modal.ts new file mode 100644 index 00000000..158087df --- /dev/null +++ b/src/utils/modal.ts @@ -0,0 +1,35 @@ +import { Modal, type ModalFuncProps } from 'antd'; +import type { ReactNode } from 'react'; + +const CANCELLED = new Error('cancelled'); + +export function confirmModal(options: ModalFuncProps) { + return new Promise((resolve, reject) => { + Modal.confirm({ + title: '系统提示', + ...options, + onOk: () => resolve(), + onCancel: () => reject(CANCELLED) + }); + }); +} + +export function confirmAction(content: ReactNode, options?: Omit) { + return confirmModal({ content, ...options }); +} + +export function confirmTitle(title: ReactNode, options?: Omit) { + return confirmModal({ title, ...options }); +} + +export async function confirmTitleSafe( + title: ReactNode, + options?: Omit +) { + try { + await confirmTitle(title, options); + return true; + } catch { + return false; + } +} diff --git a/src/utils/ruoyi.ts b/src/utils/ruoyi.ts index 1615832d..77c7f89c 100644 --- a/src/utils/ruoyi.ts +++ b/src/utils/ruoyi.ts @@ -1,4 +1,5 @@ import dayjs, { type ConfigType } from 'dayjs'; +import type { Key } from 'react'; export function tansParams(params: Record) { let result = ''; @@ -35,14 +36,20 @@ export function parseStrEmpty(value?: string | number) { return value; } -export function addDateRange>(params: T, dateRange?: [string, string] | null) { +export function addDateRange>( + params: T, + dateRange?: [string, string] | null, + propName?: string +) { if (!dateRange?.length) return params; + const beginKey = propName ? `begin${propName}` : 'beginTime'; + const endKey = propName ? `end${propName}` : 'endTime'; return { ...params, params: { ...((params.params as Record) || {}), - beginTime: dateRange[0], - endTime: dateRange[1] + [beginKey]: dateRange[0], + [endKey]: dateRange[1] } }; } @@ -61,6 +68,35 @@ export function formatDateTimeRange(dateRange?: [ConfigType, ConfigType] | null) ]; } +export function toDayjsValue(value?: ConfigType | null) { + return value ? dayjs(value) : undefined; +} + +export function formatDateTimeValue(value: unknown) { + if (!value) return value; + return dayjs(value as ConfigType).format('YYYY-MM-DD HH:mm:ss'); +} + +export function toDayjsFields>(data: T, fields: string[]) { + const next: Record = { ...data }; + for (const field of fields) { + if (next[field]) { + next[field] = toDayjsValue(next[field] as ConfigType); + } + } + return next as T; +} + +export function formatDateTimeFields>(data: T, fields: string[]) { + const next: Record = { ...data }; + for (const field of fields) { + if (next[field]) { + next[field] = formatDateTimeValue(next[field]); + } + } + return next as T; +} + export function toPageQuery(params: T & { current?: number; pageSize?: number }) { const { current, pageSize, ...rest } = params; return { @@ -138,3 +174,30 @@ export function handleTree(data: T[], id = 'id', parentId = 'parentId', child } return tree; } + +export function collectTreeKeys( + nodes: T[], + getKey: (node: T) => Key, + getChildren: (node: T) => T[] | undefined = node => (node as Record).children as T[] | undefined +): Key[] { + return nodes.flatMap(node => { + const children = getChildren(node); + return [getKey(node), ...(children?.length ? collectTreeKeys(children, getKey, getChildren) : [])]; + }); +} + +export function filterTree( + nodes: T[], + predicate: (node: T) => boolean, + children = 'children' +): T[] { + return nodes + .filter(predicate) + .map(node => { + const record = node as Record; + return { + ...node, + [children]: filterTree((record[children] as T[] | undefined) || [], predicate, children) + } as T; + }); +}