2026-01-08 16:35:06 +08:00
|
|
|
|
import { MyIcons, MyIconsType, PermissionsType, useMyState } from '@/common';
|
|
|
|
|
|
import AvatarProps from '@/common/components/layout/AvatarProps';
|
2026-01-13 15:19:57 +08:00
|
|
|
|
import { SettingOutlined } from '@ant-design/icons';
|
2026-01-08 16:35:06 +08:00
|
|
|
|
import { Link, RuntimeConfig, history, useNavigate } from '@umijs/max';
|
2026-01-13 15:19:57 +08:00
|
|
|
|
import { AutoComplete, Button, Input, Menu, MenuProps, Space } from 'antd';
|
2026-01-08 16:35:06 +08:00
|
|
|
|
import { useState } from 'react';
|
|
|
|
|
|
import './allConfig.scss';
|
|
|
|
|
|
// import Logo from './logo.png';
|
|
|
|
|
|
interface LevelKeysProps {
|
|
|
|
|
|
key?: string;
|
|
|
|
|
|
children?: LevelKeysProps[];
|
|
|
|
|
|
}
|
|
|
|
|
|
const loopMenu = (permissions: PermissionsType[] | undefined) => {
|
|
|
|
|
|
let tree: PermissionsType[] = [];
|
|
|
|
|
|
let map: Record<number, PermissionsType> = {};
|
|
|
|
|
|
// 过滤掉Button和Tab类型的权限,这些不应该显示在菜单中
|
|
|
|
|
|
const menuPermissions = permissions?.filter(
|
|
|
|
|
|
(p) => p.type !== 'Button' && p.type !== 'Tab',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
menuPermissions?.forEach((permission) => {
|
|
|
|
|
|
map[permission.id] = {
|
|
|
|
|
|
path: permission.path,
|
|
|
|
|
|
name: permission.name,
|
|
|
|
|
|
icon: permission.icon && MyIcons[permission.icon as MyIconsType],
|
|
|
|
|
|
label: permission.name,
|
|
|
|
|
|
key: permission.path || permission.id.toString(),
|
|
|
|
|
|
hideInMenu: false, // 已经过滤过类型,这里可以设置为false
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
menuPermissions?.forEach((permission) => {
|
|
|
|
|
|
let node = map[permission.id];
|
|
|
|
|
|
const parentId = permission?.parent_id;
|
|
|
|
|
|
|
|
|
|
|
|
if (parentId !== null && parentId !== undefined) {
|
|
|
|
|
|
const parentNode = map[parentId];
|
|
|
|
|
|
|
|
|
|
|
|
if (parentNode) {
|
|
|
|
|
|
// 初始化 children 如果不存在
|
|
|
|
|
|
if (!Array.isArray(parentNode.children)) {
|
|
|
|
|
|
parentNode.children = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
parentNode.children.push(node);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 父节点不存在,作为根节点处理
|
|
|
|
|
|
console.warn(
|
|
|
|
|
|
`Parent node with id ${parentId} not found for permission ${permission.id}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
tree.push(node);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
tree.push(node);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return tree?.[0]?.children;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const LayoutConfig: RuntimeConfig['layout'] = () => {
|
|
|
|
|
|
const { snap } = useMyState();
|
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
const permissionsList = (snap.session.permissions || [])
|
|
|
|
|
|
.filter((p: any) => p.type !== 'Button' && p.path)
|
|
|
|
|
|
.sort((a: any, b: any) => a._lft - b._lft)
|
|
|
|
|
|
.map((p: any) => ({ value: p.path, label: p.name }));
|
|
|
|
|
|
const quickLinks = [
|
|
|
|
|
|
{ label: '工单Bi', path: '/work_order/work_bi' },
|
|
|
|
|
|
{ label: '合同Bi', path: '/contract/contracts_bi' },
|
|
|
|
|
|
{ label: '收费Bi', path: '/charge/charge_bi' },
|
|
|
|
|
|
{ label: '项目Bi', path: '/asset/asset_bi' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const [stateOpenKeys, setStateOpenKeys] = useState(['2', '23']);
|
|
|
|
|
|
const getLevelKeys: any = (items1: LevelKeysProps[]) => {
|
|
|
|
|
|
const key: Record<string, number> = {};
|
|
|
|
|
|
const func = (items2: LevelKeysProps[], level = 1) => {
|
|
|
|
|
|
console.log(items2, 'level');
|
|
|
|
|
|
items2?.forEach((item) => {
|
|
|
|
|
|
if (item.key) {
|
|
|
|
|
|
key[item.key] = level;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (item.children) {
|
|
|
|
|
|
func(item.children, level + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
func(items1);
|
|
|
|
|
|
return key;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
title: '',
|
|
|
|
|
|
// 首页 logo
|
|
|
|
|
|
logo: (
|
|
|
|
|
|
<div style={{ width: 181 }}>
|
|
|
|
|
|
{/* <img src={Logo} style={{ height: '42px' }} /> */}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
),
|
|
|
|
|
|
layout: 'mix',
|
|
|
|
|
|
siderWidth: 180,
|
|
|
|
|
|
colorPrimary: '#1890ff',
|
|
|
|
|
|
pure: history.location.pathname === '/login',
|
|
|
|
|
|
avatarProps: {
|
|
|
|
|
|
render: () => <AvatarProps user={snap.session.user} />,
|
|
|
|
|
|
},
|
|
|
|
|
|
headerContentRender: () => (
|
|
|
|
|
|
<div className="headerContentRender">
|
|
|
|
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 20 }}>
|
|
|
|
|
|
<HeaderSearch permissionsList={permissionsList} />
|
2026-01-13 15:19:57 +08:00
|
|
|
|
{/* <Space size={20} style={{ color: '#666' }}>
|
2026-01-08 16:35:06 +08:00
|
|
|
|
常用功能:
|
|
|
|
|
|
{quickLinks.map((q) => (
|
|
|
|
|
|
<a
|
|
|
|
|
|
key={q.path}
|
|
|
|
|
|
onClick={() => history.push(q.path)}
|
|
|
|
|
|
className="quick_link"
|
|
|
|
|
|
>
|
|
|
|
|
|
{q.label}
|
|
|
|
|
|
</a>
|
|
|
|
|
|
))}
|
2026-01-13 15:19:57 +08:00
|
|
|
|
</Space> */}
|
2026-01-08 16:35:06 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<Space size={10}>
|
2026-01-13 15:19:57 +08:00
|
|
|
|
{/* <Popover
|
2026-01-08 16:35:06 +08:00
|
|
|
|
placement="bottom"
|
|
|
|
|
|
title="小程序二维码"
|
|
|
|
|
|
content={
|
|
|
|
|
|
<Space style={{ textAlign: 'center' }} size="large">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Image src={ImgEmployeeWxApp} style={{ height: '120px' }} />
|
|
|
|
|
|
<div style={{ marginTop: 10 }}>员工端</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Image src={ImgCustomerWxApp} style={{ height: '120px' }} />
|
|
|
|
|
|
<div style={{ marginTop: 10 }}>客户端</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Button type="default" shape="circle" icon={<TabletOutlined />} />
|
|
|
|
|
|
</Popover>
|
2026-01-13 15:19:57 +08:00
|
|
|
|
<Button type="default" shape="circle" icon={<BellOutlined />} /> */}
|
2026-01-08 16:35:06 +08:00
|
|
|
|
<Button
|
|
|
|
|
|
type="default"
|
|
|
|
|
|
shape="circle"
|
|
|
|
|
|
icon={<SettingOutlined />}
|
|
|
|
|
|
onClick={() => history.push('/system/sys_permissions')}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
),
|
|
|
|
|
|
//水印设置
|
|
|
|
|
|
waterMarkProps: {
|
|
|
|
|
|
content: snap.session.user?.username,
|
|
|
|
|
|
},
|
|
|
|
|
|
defaultCollapsed: true,
|
|
|
|
|
|
collapsedButtonRender: false,
|
|
|
|
|
|
token: {
|
|
|
|
|
|
bgLayout: '#f6f6f6',
|
|
|
|
|
|
// header: {
|
|
|
|
|
|
// colorBgHeader: '#1B1F3B',
|
|
|
|
|
|
// colorHeaderTitle: '#f8f8f8',
|
|
|
|
|
|
// colorTextRightActionsItem: '#FFF',
|
|
|
|
|
|
// heightLayoutHeader: 50,
|
|
|
|
|
|
// },
|
|
|
|
|
|
//菜单背景色
|
|
|
|
|
|
sider: {
|
|
|
|
|
|
colorMenuBackground: '#fff',
|
|
|
|
|
|
colorTextMenuSelected: '#1890ff', // 菜单激活项字体颜色设置为蓝色
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
// 上下菜单
|
|
|
|
|
|
menuItemRender: (item, dom) => <Link to={item.path || '/'}>{dom}</Link>,
|
|
|
|
|
|
//点击目录,收起其他菜单
|
|
|
|
|
|
menuCollapse: true,
|
|
|
|
|
|
// //左右菜单
|
|
|
|
|
|
menuRender: () => {
|
|
|
|
|
|
let objjs: any = [];
|
|
|
|
|
|
snap.session.permissions?.forEach((res: any) => {
|
|
|
|
|
|
objjs.push(res);
|
|
|
|
|
|
});
|
|
|
|
|
|
let data = objjs.sort((a: any, b: any) => {
|
|
|
|
|
|
return a._lft - b._lft;
|
|
|
|
|
|
});
|
|
|
|
|
|
const menus = loopMenu(data);
|
|
|
|
|
|
|
|
|
|
|
|
const levelKeys: any = getLevelKeys(menus as LevelKeysProps[]);
|
|
|
|
|
|
const onOpenChange: MenuProps['onOpenChange'] = (openKeys) => {
|
|
|
|
|
|
const currentOpenKey = openKeys.find(
|
|
|
|
|
|
(key) => !stateOpenKeys.includes(key),
|
|
|
|
|
|
);
|
|
|
|
|
|
// open
|
|
|
|
|
|
if (currentOpenKey !== undefined) {
|
|
|
|
|
|
const repeatIndex = openKeys
|
|
|
|
|
|
.filter((key) => key !== currentOpenKey)
|
|
|
|
|
|
.findIndex((key) => levelKeys[key] === levelKeys[currentOpenKey]);
|
|
|
|
|
|
|
|
|
|
|
|
setStateOpenKeys(
|
|
|
|
|
|
openKeys
|
|
|
|
|
|
// remove repeat key
|
|
|
|
|
|
.filter((_, index) => index !== repeatIndex)
|
|
|
|
|
|
// remove current level all child
|
|
|
|
|
|
.filter((key) => levelKeys[key] <= levelKeys[currentOpenKey]),
|
|
|
|
|
|
);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// close
|
|
|
|
|
|
setStateOpenKeys(openKeys);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
position: 'relative',
|
|
|
|
|
|
width: 180,
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
left: 0,
|
|
|
|
|
|
bottom: 0,
|
|
|
|
|
|
height: '100vh',
|
|
|
|
|
|
zIndex: 100,
|
|
|
|
|
|
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
position: 'fixed',
|
|
|
|
|
|
paddingTop: 60,
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
left: 0,
|
|
|
|
|
|
bottom: 0,
|
|
|
|
|
|
backgroundColor: '#fff',
|
|
|
|
|
|
overflowY: 'auto',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Menu
|
|
|
|
|
|
style={{ width: '165px' }}
|
|
|
|
|
|
mode="inline"
|
|
|
|
|
|
defaultSelectedKeys={[history.location.pathname]}
|
|
|
|
|
|
theme="light"
|
|
|
|
|
|
items={menus}
|
|
|
|
|
|
openKeys={stateOpenKeys}
|
|
|
|
|
|
onOpenChange={onOpenChange}
|
|
|
|
|
|
onClick={({ key }) => {
|
|
|
|
|
|
sessionStorage.setItem('breadcrumbs', '');
|
|
|
|
|
|
navigate(key);
|
|
|
|
|
|
console.log(key, 'key2');
|
|
|
|
|
|
}}
|
|
|
|
|
|
onSelect={({ key }) => {
|
|
|
|
|
|
console.log(key, 'key');
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
menuMode: 'inline',
|
|
|
|
|
|
menu: {
|
|
|
|
|
|
params: snap.session.permissions,
|
|
|
|
|
|
mode: 'inline',
|
|
|
|
|
|
request: async () => {
|
|
|
|
|
|
let objjs: any = [];
|
|
|
|
|
|
snap.session.permissions?.forEach((res: any) => {
|
|
|
|
|
|
objjs.push(res);
|
|
|
|
|
|
});
|
|
|
|
|
|
let data = objjs.sort((a: any, b: any) => {
|
|
|
|
|
|
return a._lft - b._lft;
|
|
|
|
|
|
});
|
|
|
|
|
|
const menus = loopMenu(data);
|
|
|
|
|
|
return Promise.resolve(menus);
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
// unAccessible: <div>unAccessible</div>,
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
const HeaderSearch = ({ permissionsList }: { permissionsList: any[] }) => {
|
|
|
|
|
|
const [value, setValue] = useState<string>('');
|
|
|
|
|
|
return (
|
|
|
|
|
|
<AutoComplete
|
|
|
|
|
|
value={value}
|
|
|
|
|
|
options={permissionsList}
|
|
|
|
|
|
style={{ width: 280 }}
|
|
|
|
|
|
placeholder="~输入关键字,搜索系统功能"
|
|
|
|
|
|
filterOption={(inputValue, option) =>
|
|
|
|
|
|
(option?.label as string)
|
|
|
|
|
|
?.toLowerCase()
|
|
|
|
|
|
.includes(inputValue.toLowerCase())
|
|
|
|
|
|
}
|
|
|
|
|
|
onChange={(v) => setValue(v)}
|
|
|
|
|
|
onSelect={(v) => {
|
|
|
|
|
|
setValue('');
|
|
|
|
|
|
history.push(v as string);
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
搜索:
|
|
|
|
|
|
<Input allowClear size="middle" />
|
|
|
|
|
|
</AutoComplete>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|