update 重构 封装工具 优化代码

This commit is contained in:
疯狂的狮子Li 2026-06-03 14:41:05 +08:00
parent b3e35ea84f
commit 7eccb9b8c1
36 changed files with 478 additions and 494 deletions

View File

@ -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<T extends object = Record<string, unknown>> {
title: string;
@ -60,10 +61,6 @@ function toDataNodes<T extends object>(
});
}
function collectKeys(nodes: DataNode[]): Key[] {
return nodes.flatMap(node => [node.key, ...(node.children?.length ? collectKeys(node.children) : [])]);
}
export default function TreePanel<T extends object = Record<string, unknown>>({
title,
placeholder = '请输入名称',
@ -95,7 +92,7 @@ export default function TreePanel<T extends object = Record<string, unknown>>({
() => 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(() => {

View File

@ -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<FlowTaskVO>();
const [userModalOpen, { setTrue: openUserModal, setFalse: closeUserModal }] = useBoolean(false);
const [userSelectMode, setUserSelectMode] = useState<UserSelectMode>('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 (

View File

@ -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<boolean>(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<FlowTaskVO>();
const [nextNodes, setNextNodes] = useState<FlowNextNodeVO[]>([]);
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 (

View File

@ -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<DateRangeValue>();
const applyDateRange = useCallback(
<T extends Record<string, unknown>>(queryParams: T, range: DateRangeValue = dateRange) => {
return addDateRange(queryParams, formatDateTimeRange(range), propName);
},
[dateRange, propName]
);
const resetDateRange = useCallback(() => setDateRange(undefined), []);
return {
dateRange,
setDateRange,
applyDateRange,
resetDateRange
};
}

20
src/hooks/useLoading.ts Normal file
View File

@ -0,0 +1,20 @@
import { useCallback, useState } from 'react';
export function useLoading(initialValue = false) {
const [loading, setLoading] = useState(initialValue);
const withLoading = useCallback(async <T>(task: () => Promise<T>) => {
setLoading(true);
try {
return await task();
} finally {
setLoading(false);
}
}, []);
return {
loading,
setLoading,
withLoading
};
}

View File

@ -0,0 +1,9 @@
import { useCallback, type RefObject } from 'react';
import type { ActionType } from '@ant-design/pro-components';
export function useSearchReset(actionRef: RefObject<ActionType | undefined>, resetExtras?: () => void) {
return useCallback(() => {
resetExtras?.();
setTimeout(() => actionRef.current?.reloadAndRest?.(), 0);
}, [actionRef, resetExtras]);
}

View File

@ -0,0 +1,54 @@
import { useCallback, useState } from 'react';
import type { Key } from 'react';
import { collectTreeKeys } from '@/utils/ruoyi';
interface UseTreeTableExpandOptions<T extends object> {
initialExpandAll?: boolean;
getChildren?: (row: T) => T[] | undefined;
}
export function useTreeTableExpand<T extends object>(
getKey: (row: T) => Key,
options: UseTreeTableExpandOptions<T> = {}
) {
const { initialExpandAll = false, getChildren } = options;
const [expandAll, setExpandAll] = useState(initialExpandAll);
const [expandedRowKeys, setExpandedRowKeys] = useState<Key[]>([]);
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
};
}

View File

@ -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<ActionType | undefined>(undefined);
const [form] = Form.useForm<TreeForm>();
const userInfo = useUserStore(state => state.userInfo);
const [treeOptions, setTreeOptions] = useState<TreeVO[]>([]);
const [tableRows, setTableRows] = useState<TreeVO[]>([]);
const [expandAll, setExpandAll] = useState(false);
const [expandedRowKeys, setExpandedRowKeys] = useState<Key[]>([]);
const { expandAll, expandedRowKeys, onExpandedRowsChange, syncExpandedRows, toggleExpandAll } =
useTreeTableExpand<TreeVO>(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<TreeVO>(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() {
<Button
key="expand"
icon={<SortAscendingOutlined />}
onClick={() => {
setExpandAll(value => {
const next = !value;
setExpandedRowKeys(next ? collectTreeKeys(tableRows) : []);
return next;
});
}}
onClick={() => toggleExpandAll(tableRows)}
>
/
</Button>

View File

@ -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<CacheVO>, key: string) {
return cache.info?.[key] || '-';
@ -44,17 +45,14 @@ function overviewRows(cache: Partial<CacheVO>) {
export default function MonitorCachePage() {
const [cache, setCache] = useState<Partial<CacheVO>>({});
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();

View File

@ -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<ActionType | undefined>(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);

View File

@ -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<ActionType | undefined>(undefined);

View File

@ -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<OperLogVO>();
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);

View File

@ -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<VerifyCodeResult>({ 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 (

View File

@ -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<void>((resolve, reject) => {
Modal.confirm({
title: '系统提示',
content,
onOk: () => resolve(),
onCancel: () => reject(new Error('cancelled'))
});
});
}
export default function SystemClientPage() {
const actionRef = useRef<ActionType | undefined>(undefined);
const [form] = Form.useForm<ClientForm>();

View File

@ -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<ActionType | undefined>(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);

View File

@ -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<string | number> {
return depts.reduce<Array<string | number>>((keys, dept) => {
keys.push(dept.deptId);
if (dept.children?.length) {
keys.push(...collectDeptKeys(dept.children));
}
return keys;
}, []);
}
export default function SystemDeptPage() {
const actionRef = useRef<ActionType | undefined>(undefined);
const [form] = Form.useForm<DeptForm>();
@ -71,8 +56,11 @@ export default function SystemDeptPage() {
const [modalTitle, setModalTitle] = useState('');
const [deptOptions, setDeptOptions] = useState<DeptVO[]>([]);
const [deptUserList, setDeptUserList] = useState<UserVO[]>([]);
const [expandedRowKeys, setExpandedRowKeys] = useState<readonly React.Key[]>([]);
const [lastDeptList, setLastDeptList] = useState<DeptVO[]>([]);
const { expandedRowKeys, onExpandedRowsChange, syncExpandedRows, toggleExpandAll } = useTreeTableExpand<DeptVO>(
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<DeptVO>(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<DeptVO>(res.data || [], 'deptId');
setLastDeptList(data);
syncExpandedRows(data);
return { data, success: true };
}}
toolbar={{ title: '部门列表' }}
@ -229,7 +214,7 @@ export default function SystemDeptPage() {
),
<Button
key="expand"
onClick={() => setExpandedRowKeys(expandedRowKeys.length ? [] : collectDeptKeys(lastDeptList))}
onClick={() => toggleExpandAll(lastDeptList)}
>
/
</Button>

View File

@ -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<Array<string | number>>([]);
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<MenuVO>[] = [

View File

@ -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 = '<p>暂无公告内容</p>';
function dictOptions(dicts?: DictData[]) {
return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue }));
}
function SafeHtmlContent({ html }: { html: string }) {
const ref = useRef<HTMLDivElement>(null);

View File

@ -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<void>((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 <Tag color="orange">private</Tag>;
if (value === '1') return <Tag color="green">public</Tag>;

View File

@ -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');

View File

@ -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;

View File

@ -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<void>((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<ActionType | undefined>(undefined);
const [form] = Form.useForm<RoleForm>();
@ -78,6 +62,7 @@ export default function SystemRolePage() {
const [permissionOpen, { setTrue: openPermissionModal, setFalse: closePermissionModal }] = useBoolean(false);
const [permissionRoleId, setPermissionRoleId] = useState<string | number>();
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);

View File

@ -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<DeptTreeVO[]>((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<void>((resolve, reject) => {
Modal.confirm({
title: '系统提示',
content,
onOk: () => resolve(),
onCancel: () => reject(new Error('cancelled'))
});
});
}
export default function SystemUserPage() {
const actionRef = useRef<ActionType | undefined>(undefined);
@ -116,6 +91,7 @@ export default function SystemUserPage() {
const [viewOpen, { setTrue: openViewDrawer, setFalse: closeViewDrawer }] = useBoolean(false);
const [viewUserId, setViewUserId] = useState<string | number>();
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);

View File

@ -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<UserInfoVO>();
const [devices, setDevices] = useState<OnlineVO[]>([]);
const [auths, setAuths] = useState<SocialAuthVO[]>([]);
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) => {

View File

@ -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);
}}

View File

@ -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<ActionType | undefined>(undefined);
const [form] = Form.useForm<CategoryForm>();
const userInfo = useUserStore(state => state.userInfo);
const [categoryOptions, setCategoryOptions] = useState<CategoryVO[]>([]);
const [tableRows, setTableRows] = useState<CategoryVO[]>([]);
const [expandAll, setExpandAll] = useState(false);
const [expandedRowKeys, setExpandedRowKeys] = useState<Key[]>([]);
const { expandAll, expandedRowKeys, onExpandedRowsChange, syncExpandedRows, toggleExpandAll } =
useTreeTableExpand<CategoryVO>(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<CategoryVO>(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() {
<Button
key="expand"
icon={<SortAscendingOutlined />}
onClick={() => {
setExpandAll(value => {
const next = !value;
setExpandedRowKeys(next ? collectCategoryKeys(tableRows) : []);
return next;
});
}}
onClick={() => toggleExpandAll(tableRows)}
>
/
</Button>

View File

@ -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 || '-';

View File

@ -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<void>((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<FlowDefinitionVO>[] = [
{
title: '流程定义名称',

View File

@ -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<R<PageResult<FlowInstanceVO>>> {
if (tab === 'running') return pageByRunning(query);
@ -58,17 +58,6 @@ function openBusinessForm(row: FlowInstanceVO) {
});
}
function confirmAction(content: string) {
return new Promise<void>((resolve, reject) => {
Modal.confirm({
title: '系统提示',
content,
onOk: () => resolve(),
onCancel: () => reject(new Error('cancelled'))
});
});
}
export default function WorkflowProcessInstancePage() {
const actionRef = useRef<ActionType | undefined>(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<FlowInstanceVO>();
const [variableOpen, { setTrue: openVariableModal, setFalse: closeVariableModal }] = useBoolean(false);
const [variableLoading, setVariableLoading] = useState(false);
const { loading: variableLoading, withLoading: withVariableLoading } = useLoading();
const [variableRow, setVariableRow] = useState<FlowInstanceVO>();
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<string | number>;
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 () => {

View File

@ -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<SpelForm, 'componentName' | 'methodName' | 'methodParams'>) {
const comp = (values.componentName || '').trim();

View File

@ -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<R<PageResult<FlowTaskVO>>> {
if (tab === 'waiting') return pageByAllTaskWait(query);
@ -40,16 +39,6 @@ function openBusinessForm(row: FlowTaskVO) {
});
}
function confirmAction(title: string) {
return new Promise<void>((resolve, reject) => {
Modal.confirm({
title,
onOk: () => resolve(),
onCancel: () => reject(new Error('cancelled'))
});
});
}
export default function WorkflowAllTaskWaitingPage() {
const actionRef = useRef<ActionType | undefined>(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<string | number>;
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();

View File

@ -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';

View File

@ -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<TaskListType, string> = {
copy: '抄送任务'
};
function dictOptions(dicts?: DictData[]) {
return (dicts || []).map(item => ({ label: item.dictLabel, value: item.dictValue }));
}
function requestTaskList(type: TaskListType, query: TaskQuery): Promise<R<PageResult<FlowTaskVO>>> {
if (type === 'wait') return pageByTaskWait(query);

9
src/utils/dict.ts Normal file
View File

@ -0,0 +1,9 @@
import type { DictData } from '@/api/system/dict/data/types';
export function dictOptions(dicts?: Pick<DictData, 'dictLabel' | 'dictValue'>[]) {
return (dicts || []).map(item => ({
label: item.dictLabel,
value: item.dictValue
}));
}

35
src/utils/modal.ts Normal file
View File

@ -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<void>((resolve, reject) => {
Modal.confirm({
title: '系统提示',
...options,
onOk: () => resolve(),
onCancel: () => reject(CANCELLED)
});
});
}
export function confirmAction(content: ReactNode, options?: Omit<ModalFuncProps, 'content' | 'onOk' | 'onCancel'>) {
return confirmModal({ content, ...options });
}
export function confirmTitle(title: ReactNode, options?: Omit<ModalFuncProps, 'title' | 'onOk' | 'onCancel'>) {
return confirmModal({ title, ...options });
}
export async function confirmTitleSafe(
title: ReactNode,
options?: Omit<ModalFuncProps, 'title' | 'onOk' | 'onCancel'>
) {
try {
await confirmTitle(title, options);
return true;
} catch {
return false;
}
}

View File

@ -1,4 +1,5 @@
import dayjs, { type ConfigType } from 'dayjs';
import type { Key } from 'react';
export function tansParams(params: Record<string, unknown>) {
let result = '';
@ -35,14 +36,20 @@ export function parseStrEmpty(value?: string | number) {
return value;
}
export function addDateRange<T extends Record<string, unknown>>(params: T, dateRange?: [string, string] | null) {
export function addDateRange<T extends Record<string, unknown>>(
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<string, unknown>) || {}),
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<T extends Record<string, unknown>>(data: T, fields: string[]) {
const next: Record<string, unknown> = { ...data };
for (const field of fields) {
if (next[field]) {
next[field] = toDayjsValue(next[field] as ConfigType);
}
}
return next as T;
}
export function formatDateTimeFields<T extends Record<string, unknown>>(data: T, fields: string[]) {
const next: Record<string, unknown> = { ...data };
for (const field of fields) {
if (next[field]) {
next[field] = formatDateTimeValue(next[field]);
}
}
return next as T;
}
export function toPageQuery<T extends object>(params: T & { current?: number; pageSize?: number }) {
const { current, pageSize, ...rest } = params;
return {
@ -138,3 +174,30 @@ export function handleTree<T>(data: T[], id = 'id', parentId = 'parentId', child
}
return tree;
}
export function collectTreeKeys<T extends object>(
nodes: T[],
getKey: (node: T) => Key,
getChildren: (node: T) => T[] | undefined = node => (node as Record<string, unknown>).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<T extends object>(
nodes: T[],
predicate: (node: T) => boolean,
children = 'children'
): T[] {
return nodes
.filter(predicate)
.map(node => {
const record = node as Record<string, unknown>;
return {
...node,
[children]: filterTree((record[children] as T[] | undefined) || [], predicate, children)
} as T;
});
}