前端功能遗漏修复
parent
b13a2bfc87
commit
6e897552d0
|
|
@ -8,7 +8,7 @@ import type { MenuProps } from 'antd/es/menu';
|
|||
import { DownOutlined, LogoutOutlined, MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { removeToken } from '../../utils/auth';
|
||||
import { usePermission } from '@/contexts/PermissionContext';
|
||||
import { clearPermissionCache, usePermission } from '@/contexts/PermissionContext';
|
||||
import './navbar.css';
|
||||
|
||||
interface AppNavbarProps {
|
||||
|
|
@ -193,7 +193,8 @@ const isPinnedTab = (tab: OpenPageTab) => tab.pathname === '/index';
|
|||
const AppNavbar: React.FC<AppNavbarProps> = ({ collapsed, onToggle }) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { userName, canAccessPath } = usePermission();
|
||||
const { userName, currentUser, canAccessPath } = usePermission();
|
||||
const displayName = String(currentUser.nickName || userName || '用户');
|
||||
const activeTab = React.useMemo(
|
||||
() => createOpenPageTab(location.pathname, location.search),
|
||||
[location.pathname, location.search],
|
||||
|
|
@ -201,6 +202,7 @@ const AppNavbar: React.FC<AppNavbarProps> = ({ collapsed, onToggle }) => {
|
|||
const [openTabs, setOpenTabs] = React.useState<OpenPageTab[]>(() => getStoredOpenPageTabs(canAccessPath));
|
||||
|
||||
const handleLogout = () => {
|
||||
clearPermissionCache();
|
||||
removeToken();
|
||||
navigate('/login');
|
||||
};
|
||||
|
|
@ -331,7 +333,7 @@ const AppNavbar: React.FC<AppNavbarProps> = ({ collapsed, onToggle }) => {
|
|||
<Button type="text" className="app-navbar-user">
|
||||
<Space size={10}>
|
||||
<Avatar size={36} className="app-navbar-avatar" icon={<UserOutlined />} />
|
||||
<span className="app-navbar-user-name">{userName || '用户'}</span>
|
||||
<span className="app-navbar-user-name">{displayName}</span>
|
||||
<DownOutlined className="app-navbar-chevron" />
|
||||
</Space>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ const SUPER_PERMI = '*:*:*';
|
|||
const ADMIN_ROLE = 'admin';
|
||||
const PERMISSION_CACHE_KEY = 'pms_permission_cache_v1';
|
||||
const PERMISSION_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
const PERMISSION_CHANGED_EVENT = 'pms:permission-changed';
|
||||
|
||||
interface PermissionCacheState {
|
||||
tokenFingerprint: string;
|
||||
|
|
@ -162,13 +163,21 @@ const writePermissionCache = (state: Omit<PermissionCacheState, 'tokenFingerprin
|
|||
}
|
||||
};
|
||||
|
||||
const clearPermissionCache = () => {
|
||||
export const clearPermissionCache = () => {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
window.sessionStorage.removeItem(PERMISSION_CACHE_KEY);
|
||||
};
|
||||
|
||||
export const notifyPermissionChanged = () => {
|
||||
clearPermissionCache();
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
window.dispatchEvent(new Event(PERMISSION_CHANGED_EVENT));
|
||||
};
|
||||
|
||||
const normalizePath = (rawPath: string) => {
|
||||
const path = rawPath.split('?')[0]?.split('#')[0] ?? '';
|
||||
if (!path) {
|
||||
|
|
@ -453,6 +462,16 @@ export const PermissionProvider: React.FC<{ children: React.ReactNode }> = ({ ch
|
|||
void refreshPermissions();
|
||||
}, [refreshPermissions]);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePermissionChanged = () => {
|
||||
void refreshPermissions();
|
||||
};
|
||||
window.addEventListener(PERMISSION_CHANGED_EVENT, handlePermissionChanged);
|
||||
return () => {
|
||||
window.removeEventListener(PERMISSION_CHANGED_EVENT, handlePermissionChanged);
|
||||
};
|
||||
}, [refreshPermissions]);
|
||||
|
||||
const roleSet = useMemo(() => new Set(roles), [roles]);
|
||||
const permissionSet = useMemo(() => new Set(permissions), [permissions]);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { login, getCodeImg } from '../../api/login';
|
|||
import { TokenKey } from '../../utils/auth';
|
||||
import type { LoginRequest } from '@/types/api';
|
||||
import { notify } from '@/utils/notify';
|
||||
import { clearPermissionCache } from '@/contexts/PermissionContext';
|
||||
import './login.css';
|
||||
|
||||
interface LoginFormValues {
|
||||
|
|
@ -86,6 +87,7 @@ const LoginPage = () => {
|
|||
if (!tokenToSet) {
|
||||
throw new Error('登录返回缺少 token');
|
||||
}
|
||||
clearPermissionCache();
|
||||
Cookies.set(TokenKey, tokenToSet);
|
||||
if (values.rememberMe) {
|
||||
Cookies.set(REMEMBER_ME_KEY, 'true', { expires: 30 });
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ interface UserInfoProps {
|
|||
const UserInfo = ({ user, onUpdated }: UserInfoProps) => {
|
||||
const [form] = Form.useForm<UpdateUserProfilePayload>();
|
||||
const navigate = useNavigate();
|
||||
const { defaultRoutePath } = usePermission();
|
||||
const { defaultRoutePath, refreshPermissions } = usePermission();
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
|
|
@ -38,6 +38,7 @@ const UserInfo = ({ user, onUpdated }: UserInfoProps) => {
|
|||
await updateUserProfile(values);
|
||||
notify.success('修改成功');
|
||||
onUpdated(values);
|
||||
await refreshPermissions();
|
||||
} catch (error) {
|
||||
console.error('Failed to update profile:', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import {
|
|||
} from '@/api/system/role';
|
||||
import PageBackButton from '@/components/PageBackButton';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import { notifyPermissionChanged } from '@/contexts/PermissionContext';
|
||||
import './system-admin.css';
|
||||
|
||||
interface UserRecord {
|
||||
|
|
@ -182,6 +183,7 @@ const RoleAuthUserPage = () => {
|
|||
userId: record.userId,
|
||||
});
|
||||
message.success('取消授权成功');
|
||||
notifyPermissionChanged();
|
||||
await refreshBoth();
|
||||
} catch (error) {
|
||||
console.error('Failed to cancel role auth:', error);
|
||||
|
|
@ -204,6 +206,7 @@ const RoleAuthUserPage = () => {
|
|||
});
|
||||
message.success('批量取消授权成功');
|
||||
setSelectedAllocatedKeys([]);
|
||||
notifyPermissionChanged();
|
||||
await refreshBoth();
|
||||
} catch (error) {
|
||||
console.error('Failed to cancel users in batch:', error);
|
||||
|
|
@ -225,6 +228,7 @@ const RoleAuthUserPage = () => {
|
|||
message.success('分配用户成功');
|
||||
setSelectedUnallocatedKeys([]);
|
||||
setActiveTab('allocated');
|
||||
notifyPermissionChanged();
|
||||
await refreshBoth();
|
||||
} catch (error) {
|
||||
console.error('Failed to assign users:', error);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { parseTime } from '../../utils/ruoyi'; // Custom utility
|
|||
import Permission from '@/components/Permission';
|
||||
import ReadonlyAction from '@/components/Permission/ReadonlyAction';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { notifyPermissionChanged } from '@/contexts/PermissionContext';
|
||||
import './system-admin.css';
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
|
|
@ -136,11 +137,40 @@ const RolePage: React.FC = () => {
|
|||
return [];
|
||||
};
|
||||
|
||||
const parseTreeLinkage = (value: unknown, fallback = true) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return fallback;
|
||||
}
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return value === 1;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
return normalized === 'true' || normalized === '1';
|
||||
}
|
||||
return Boolean(value);
|
||||
};
|
||||
|
||||
const getTreeNodeKey = (node: any): React.Key | undefined => node?.id ?? node?.key;
|
||||
|
||||
const uniqueKeys = (keys: React.Key[]) => {
|
||||
const keyMap = new Map<string, React.Key>();
|
||||
keys.forEach((key) => {
|
||||
if (key !== undefined && key !== null && String(key) !== '') {
|
||||
keyMap.set(String(key), key);
|
||||
}
|
||||
});
|
||||
return Array.from(keyMap.values());
|
||||
};
|
||||
|
||||
const collectTreeKeys = (nodes: any[]): React.Key[] => {
|
||||
const keys: React.Key[] = [];
|
||||
const visit = (list: any[]) => {
|
||||
list.forEach((node) => {
|
||||
const key = node?.id ?? node?.key;
|
||||
const key = getTreeNodeKey(node);
|
||||
if (key !== undefined) {
|
||||
keys.push(key);
|
||||
}
|
||||
|
|
@ -150,7 +180,67 @@ const RolePage: React.FC = () => {
|
|||
});
|
||||
};
|
||||
visit(nodes ?? []);
|
||||
return keys;
|
||||
return uniqueKeys(keys);
|
||||
};
|
||||
|
||||
const normalizeLinkedTreeSelection = (keys: React.Key[], nodes: any[]) => {
|
||||
const checkedKeySet = new Set(uniqueKeys(keys).map((key) => String(key)));
|
||||
const checkedKeys: React.Key[] = [];
|
||||
const halfCheckedKeys: React.Key[] = [];
|
||||
|
||||
const hasCheckedDescendant = (node: any): boolean => {
|
||||
const children = Array.isArray(node?.children) ? node.children : [];
|
||||
return children.some((child: any) => {
|
||||
const childKey = getTreeNodeKey(child);
|
||||
return (
|
||||
(childKey !== undefined && checkedKeySet.has(String(childKey))) ||
|
||||
hasCheckedDescendant(child)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const allLeafDescendantsChecked = (node: any): boolean => {
|
||||
const children = Array.isArray(node?.children) ? node.children : [];
|
||||
if (children.length === 0) {
|
||||
const key = getTreeNodeKey(node);
|
||||
return key !== undefined && checkedKeySet.has(String(key));
|
||||
}
|
||||
return children.every((child: any) => allLeafDescendantsChecked(child));
|
||||
};
|
||||
|
||||
const visit = (list: any[]) => {
|
||||
list.forEach((node) => {
|
||||
const key = getTreeNodeKey(node);
|
||||
const children = Array.isArray(node?.children) ? node.children : [];
|
||||
if (key !== undefined && checkedKeySet.has(String(key))) {
|
||||
const isPartialParent = children.length > 0 && hasCheckedDescendant(node) && !allLeafDescendantsChecked(node);
|
||||
if (isPartialParent) {
|
||||
halfCheckedKeys.push(key);
|
||||
} else {
|
||||
checkedKeys.push(key);
|
||||
}
|
||||
}
|
||||
if (children.length > 0) {
|
||||
visit(children);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
visit(nodes ?? []);
|
||||
return {
|
||||
checkedKeys: uniqueKeys(checkedKeys),
|
||||
halfCheckedKeys: uniqueKeys(halfCheckedKeys),
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeTreeSelection = (keys: React.Key[], nodes: any[], linkChildren: boolean) => {
|
||||
if (!linkChildren) {
|
||||
return {
|
||||
checkedKeys: uniqueKeys(keys),
|
||||
halfCheckedKeys: [],
|
||||
};
|
||||
}
|
||||
return normalizeLinkedTreeSelection(keys, nodes);
|
||||
};
|
||||
|
||||
const getList = useCallback(async () => {
|
||||
|
|
@ -242,6 +332,7 @@ const RolePage: React.FC = () => {
|
|||
try {
|
||||
await changeRoleStatus(record.roleId, newStatus);
|
||||
message.success(`${text}成功`);
|
||||
notifyPermissionChanged();
|
||||
getList();
|
||||
} catch (error) {
|
||||
message.error(`${text}失败`);
|
||||
|
|
@ -301,11 +392,15 @@ const RolePage: React.FC = () => {
|
|||
const roleResponse = await getRole(roleId);
|
||||
|
||||
const roleData = extractResponseData(roleResponse);
|
||||
const nextMenuCheckStrictly = parseTreeLinkage(roleData.menuCheckStrictly, true);
|
||||
const nextDeptCheckStrictly = parseTreeLinkage(roleData.deptCheckStrictly, true);
|
||||
const nextMenuOptions = extractTreeNodes(roleMenuResponse, 'menus');
|
||||
const nextMenuSelection = normalizeTreeSelection(extractCheckedKeys(roleMenuResponse), nextMenuOptions, nextMenuCheckStrictly);
|
||||
setCurrentRole(roleData);
|
||||
setMenuCheckStrictly(roleData.menuCheckStrictly === undefined ? true : !!roleData.menuCheckStrictly);
|
||||
setDeptCheckStrictly(roleData.deptCheckStrictly === undefined ? true : !!roleData.deptCheckStrictly);
|
||||
setMenuCheckedKeys(extractCheckedKeys(roleMenuResponse));
|
||||
setMenuHalfCheckedKeys([]);
|
||||
setMenuCheckStrictly(nextMenuCheckStrictly);
|
||||
setDeptCheckStrictly(nextDeptCheckStrictly);
|
||||
setMenuCheckedKeys(nextMenuSelection.checkedKeys);
|
||||
setMenuHalfCheckedKeys(nextMenuSelection.halfCheckedKeys);
|
||||
setAddEditModalTitle('修改角色');
|
||||
setFormType('edit');
|
||||
|
||||
|
|
@ -335,6 +430,7 @@ const RolePage: React.FC = () => {
|
|||
await delRole(roleIds.join(','));
|
||||
message.success('删除成功');
|
||||
setSelectedRowKeys([]);
|
||||
notifyPermissionChanged();
|
||||
getList();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
|
|
@ -388,15 +484,19 @@ const RolePage: React.FC = () => {
|
|||
const deptTreeResponse = await getDeptTreeselect(roleId); // Get dept tree with selected keys
|
||||
const roleResponse = await getRole(roleId);
|
||||
const roleData = extractResponseData(roleResponse);
|
||||
const nextDeptCheckStrictly = parseTreeLinkage(roleData.deptCheckStrictly, true);
|
||||
const nextDeptOptions = extractTreeNodes(deptTreeResponse, 'depts');
|
||||
const nextDeptSelection = normalizeTreeSelection(extractCheckedKeys(deptTreeResponse), nextDeptOptions, nextDeptCheckStrictly);
|
||||
setCurrentRole(roleData);
|
||||
setDeptCheckStrictly(nextDeptCheckStrictly);
|
||||
setDataScopeModalTitle('分配数据权限');
|
||||
setDataScopeModalVisible(true);
|
||||
dataScopeForm.setFieldsValue({
|
||||
...roleData,
|
||||
dataScope: roleData.dataScope?.toString(),
|
||||
});
|
||||
setDeptCheckedKeys(extractCheckedKeys(deptTreeResponse));
|
||||
setDeptHalfCheckedKeys([]);
|
||||
setDeptCheckedKeys(nextDeptSelection.checkedKeys);
|
||||
setDeptHalfCheckedKeys(nextDeptSelection.halfCheckedKeys);
|
||||
} catch (error) {
|
||||
message.error('获取数据权限信息失败');
|
||||
}
|
||||
|
|
@ -408,7 +508,7 @@ const RolePage: React.FC = () => {
|
|||
|
||||
const submitRoleForm = async (values: any) => {
|
||||
try {
|
||||
const menuIds = [...menuCheckedKeys, ...menuHalfCheckedKeys];
|
||||
const menuIds = uniqueKeys([...menuCheckedKeys, ...menuHalfCheckedKeys]);
|
||||
const roleData = { ...currentRole, ...values, menuIds, menuCheckStrictly };
|
||||
|
||||
if (formType === 'edit') {
|
||||
|
|
@ -419,6 +519,7 @@ const RolePage: React.FC = () => {
|
|||
message.success('新增成功');
|
||||
}
|
||||
setAddEditModalVisible(false);
|
||||
notifyPermissionChanged();
|
||||
getList();
|
||||
} catch (error) {
|
||||
console.error('Submit role form failed:', error);
|
||||
|
|
@ -430,13 +531,14 @@ const RolePage: React.FC = () => {
|
|||
try {
|
||||
let deptIds: React.Key[] = [];
|
||||
if (values.dataScope === '2') {
|
||||
deptIds = [...deptCheckedKeys, ...deptHalfCheckedKeys];
|
||||
deptIds = uniqueKeys([...deptCheckedKeys, ...deptHalfCheckedKeys]);
|
||||
}
|
||||
const roleData = { ...currentRole, ...values, deptIds, deptCheckStrictly };
|
||||
|
||||
await dataScope(roleData);
|
||||
message.success('分配数据权限成功');
|
||||
setDataScopeModalVisible(false);
|
||||
notifyPermissionChanged();
|
||||
getList();
|
||||
} catch (error) {
|
||||
message.error('分配数据权限失败');
|
||||
|
|
@ -477,27 +579,37 @@ const RolePage: React.FC = () => {
|
|||
|
||||
const handleMenuCheck = (checked: any, info: any) => {
|
||||
if (Array.isArray(checked)) {
|
||||
setMenuCheckedKeys(checked);
|
||||
setMenuHalfCheckedKeys(info?.halfCheckedKeys ?? []);
|
||||
setMenuCheckedKeys(uniqueKeys(checked));
|
||||
setMenuHalfCheckedKeys(uniqueKeys(info?.halfCheckedKeys ?? []));
|
||||
return;
|
||||
}
|
||||
setMenuCheckedKeys(checked?.checked ?? []);
|
||||
setMenuHalfCheckedKeys(checked?.halfChecked ?? []);
|
||||
setMenuCheckedKeys(uniqueKeys(checked?.checked ?? []));
|
||||
setMenuHalfCheckedKeys(uniqueKeys(checked?.halfChecked ?? []));
|
||||
};
|
||||
|
||||
const handleDeptCheck = (checked: any, info: any) => {
|
||||
if (Array.isArray(checked)) {
|
||||
setDeptCheckedKeys(checked);
|
||||
setDeptHalfCheckedKeys(info?.halfCheckedKeys ?? []);
|
||||
setDeptCheckedKeys(uniqueKeys(checked));
|
||||
setDeptHalfCheckedKeys(uniqueKeys(info?.halfCheckedKeys ?? []));
|
||||
return;
|
||||
}
|
||||
setDeptCheckedKeys(checked?.checked ?? []);
|
||||
setDeptHalfCheckedKeys(checked?.halfChecked ?? []);
|
||||
setDeptCheckedKeys(uniqueKeys(checked?.checked ?? []));
|
||||
setDeptHalfCheckedKeys(uniqueKeys(checked?.halfChecked ?? []));
|
||||
};
|
||||
|
||||
const handleCheckedTreeConnect = (value: boolean, type: 'menu' | 'dept') => {
|
||||
if (type === 'menu') setMenuCheckStrictly(value);
|
||||
if (type === 'dept') setDeptCheckStrictly(value);
|
||||
if (type === 'menu') {
|
||||
const nextSelection = normalizeTreeSelection([...menuCheckedKeys, ...menuHalfCheckedKeys], menuOptions, value);
|
||||
setMenuCheckedKeys(nextSelection.checkedKeys);
|
||||
setMenuHalfCheckedKeys(nextSelection.halfCheckedKeys);
|
||||
setMenuCheckStrictly(value);
|
||||
}
|
||||
if (type === 'dept') {
|
||||
const nextSelection = normalizeTreeSelection([...deptCheckedKeys, ...deptHalfCheckedKeys], deptOptions, value);
|
||||
setDeptCheckedKeys(nextSelection.checkedKeys);
|
||||
setDeptHalfCheckedKeys(nextSelection.halfCheckedKeys);
|
||||
setDeptCheckStrictly(value);
|
||||
}
|
||||
};
|
||||
|
||||
const columns: TableColumnsType<any> = [
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { useParams } from 'react-router-dom';
|
|||
import { getAuthRole, getUser, updateAuthRole } from '@/api/system/user';
|
||||
import PageBackButton from '@/components/PageBackButton';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import { notifyPermissionChanged } from '@/contexts/PermissionContext';
|
||||
import './system-admin.css';
|
||||
|
||||
interface RoleRecord {
|
||||
|
|
@ -94,6 +95,7 @@ const UserAuthRolePage = () => {
|
|||
roleIds: selectedRoleIds.join(','),
|
||||
});
|
||||
message.success('分配角色成功');
|
||||
notifyPermissionChanged();
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
console.error('Failed to save user roles:', error);
|
||||
|
|
|
|||
|
|
@ -210,16 +210,56 @@ const normalizeFileList = (value: unknown): FileRecord[] =>
|
|||
}))
|
||||
: [];
|
||||
|
||||
const pickFirstValue = (source: Record<string, unknown>, keys: string[]) => {
|
||||
for (const key of keys) {
|
||||
const value = source[key];
|
||||
if (value !== undefined && value !== null && String(value).trim() !== '') {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const extractRows = (value: unknown): unknown[] => {
|
||||
if (Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!isObject(value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const directArray = pickFirstValue(value, ['rows', 'records', 'list', 'projectList', 'projects']);
|
||||
if (Array.isArray(directArray)) {
|
||||
return directArray;
|
||||
}
|
||||
|
||||
if (isObject(value.data)) {
|
||||
return extractRows(value.data);
|
||||
}
|
||||
|
||||
if (Array.isArray(value.data)) {
|
||||
return value.data;
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const normalizeProjectRows = (value: unknown): ProjectRow[] =>
|
||||
Array.isArray(value)
|
||||
? value
|
||||
.filter((item) => isObject(item))
|
||||
.map((item) => ({
|
||||
.map((item) => {
|
||||
const projectId = pickFirstValue(item, ['projectId', 'id', 'project_id', 'businessProjectId']);
|
||||
const projectName = pickFirstValue(item, ['projectName', 'name', 'project_name', 'title']);
|
||||
return {
|
||||
...item,
|
||||
projectId: item.projectId as string | number | undefined,
|
||||
projectName: String(item.projectName ?? item.name ?? ''),
|
||||
name: String(item.name ?? item.projectName ?? ''),
|
||||
}))
|
||||
projectId: projectId as string | number | undefined,
|
||||
projectName: String(projectName ?? ''),
|
||||
name: String(projectName ?? ''),
|
||||
};
|
||||
})
|
||||
.filter((item) => item.projectId !== undefined && item.projectId !== null && String(item.projectId) !== '')
|
||||
: [];
|
||||
|
||||
const PROJECT_BOUNDARY_KEYS: Array<keyof ProjectRow> = [
|
||||
|
|
@ -287,7 +327,7 @@ const buildWorkLogPayload = (row: WorkLogRow): Record<string, unknown> => ({
|
|||
fileList: normalizeFileList(row.fileList),
|
||||
});
|
||||
|
||||
let inflightFallbackProjectListRequest: Promise<unknown> | null = null;
|
||||
const inflightFallbackProjectListRequests = new Map<string, Promise<unknown>>();
|
||||
const inflightWorklogUserProjectRequests = new Map<string, Promise<unknown>>();
|
||||
const inflightCalendarRequests = new Map<string, Promise<unknown>>();
|
||||
const inflightDayListRequests = new Map<string, Promise<unknown>>();
|
||||
|
|
@ -307,15 +347,22 @@ const fetchUserProjectListRequest = async (userId: string) => {
|
|||
return requestPromise;
|
||||
};
|
||||
|
||||
const fetchFallbackProjectListRequest = async () => {
|
||||
if (inflightFallbackProjectListRequest) {
|
||||
return inflightFallbackProjectListRequest;
|
||||
const fetchFallbackProjectListRequest = async (userId?: string) => {
|
||||
const normalizedUserId = String(userId ?? '').trim();
|
||||
const requestKey = normalizedUserId || '__current__';
|
||||
const existingRequest = inflightFallbackProjectListRequests.get(requestKey);
|
||||
if (existingRequest) {
|
||||
return existingRequest;
|
||||
}
|
||||
|
||||
const requestPromise = listProject({ pageNum: 1, pageSize: 10000 }).finally(() => {
|
||||
inflightFallbackProjectListRequest = null;
|
||||
const requestPromise = listProject({
|
||||
pageNum: 1,
|
||||
pageSize: 10000,
|
||||
queryUserId: normalizedUserId || undefined,
|
||||
}).finally(() => {
|
||||
inflightFallbackProjectListRequests.delete(requestKey);
|
||||
});
|
||||
inflightFallbackProjectListRequest = requestPromise;
|
||||
inflightFallbackProjectListRequests.set(requestKey, requestPromise);
|
||||
return requestPromise;
|
||||
};
|
||||
|
||||
|
|
@ -417,9 +464,9 @@ const WorkLogPage = () => {
|
|||
}, [currentUser.userId, queryUserId]);
|
||||
|
||||
const disableTable = isReadOnlyByQuery || dayReadonly;
|
||||
const canAddWorkLog = hasPermi('worklog/worklog:list');
|
||||
const canEditWorkLog = hasPermi('worklog/worklog:list');
|
||||
const canDeleteWorkLog = hasPermi('worklog/worklog:list');
|
||||
const canAddWorkLog = hasPermi(['business:work:hour:add', 'business:work:hour:list', 'worklog/worklog:list']);
|
||||
const canEditWorkLog = hasPermi(['business:work:hour:edit', 'business:work:hour:update', 'business:work:hour:list', 'worklog/worklog:list']);
|
||||
const canDeleteWorkLog = hasPermi(['business:work:hour:remove', 'business:work:hour:list', 'worklog/worklog:list']);
|
||||
const showDetailBack = useMemo(() => Boolean(queryUserId || queryProjectId), [queryProjectId, queryUserId]);
|
||||
const displayUserName = queryNickName || currentUser.nickName || currentUser.userName || '当日日志';
|
||||
const detailFallbackPath = useMemo(() => {
|
||||
|
|
@ -533,21 +580,17 @@ const WorkLogPage = () => {
|
|||
return;
|
||||
}
|
||||
const payload = normalizeResponseData(response);
|
||||
const rows = isObject(payload) && Array.isArray(payload.rows) ? payload.rows : Array.isArray(payload) ? payload : [];
|
||||
const rows = extractRows(payload);
|
||||
const normalizedRows = normalizeProjectRows(rows);
|
||||
|
||||
if (!normalizedRows.some(hasProjectBoundaryInfo)) {
|
||||
try {
|
||||
const detailResponse = await fetchFallbackProjectListRequest();
|
||||
const detailResponse = await fetchFallbackProjectListRequest(effectiveUserId);
|
||||
if (requestId !== projectListRequestIdRef.current) {
|
||||
return;
|
||||
}
|
||||
const detailPayload = normalizeResponseData(detailResponse);
|
||||
const detailRows = isObject(detailPayload) && Array.isArray(detailPayload.rows)
|
||||
? detailPayload.rows
|
||||
: Array.isArray(detailPayload)
|
||||
? detailPayload
|
||||
: [];
|
||||
const detailRows = extractRows(detailPayload);
|
||||
setProjectList(mergeProjectRowsWithDetails(normalizedRows, normalizeProjectRows(detailRows)));
|
||||
return;
|
||||
} catch (detailError) {
|
||||
|
|
@ -562,12 +605,12 @@ const WorkLogPage = () => {
|
|||
}
|
||||
|
||||
try {
|
||||
const response = await fetchFallbackProjectListRequest();
|
||||
const response = await fetchFallbackProjectListRequest(effectiveUserId);
|
||||
if (requestId !== projectListRequestIdRef.current) {
|
||||
return;
|
||||
}
|
||||
const payload = normalizeResponseData(response);
|
||||
const rows = isObject(payload) && Array.isArray(payload.rows) ? payload.rows : Array.isArray(payload) ? payload : [];
|
||||
const rows = extractRows(payload);
|
||||
setProjectList(normalizeProjectRows(rows));
|
||||
} catch (error) {
|
||||
if (requestId !== projectListRequestIdRef.current) {
|
||||
|
|
@ -741,7 +784,7 @@ const WorkLogPage = () => {
|
|||
}, [fetchDayList]);
|
||||
|
||||
const projectListFilter = useMemo(() => {
|
||||
return projectList.filter((item) => {
|
||||
const matchedProjects = projectList.filter((item) => {
|
||||
const start = pickProjectBoundaryDate(item, [
|
||||
'startDate',
|
||||
'startTime',
|
||||
|
|
@ -769,6 +812,7 @@ const WorkLogPage = () => {
|
|||
}
|
||||
return true;
|
||||
});
|
||||
return matchedProjects.length > 0 ? matchedProjects : projectList;
|
||||
}, [projectList, selectedDayDate]);
|
||||
|
||||
const totalWorkTime = useMemo(() => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue