This commit is contained in:
2026-01-22 16:52:40 +08:00
parent ab91458199
commit a6be3aa5e2
13 changed files with 341 additions and 155 deletions

View File

@@ -1,4 +1,5 @@
import { Button, Popconfirm } from 'antd'; import { DownOutlined } from '@ant-design/icons';
import { Button, Dropdown, Popconfirm, Space } from 'antd';
import { useUserStore } from '@/store/UserStore'; import { useUserStore } from '@/store/UserStore';
import { GapBox } from '../GapBox'; import { GapBox } from '../GapBox';
@@ -7,7 +8,51 @@ export const HeaderUserInfo: React.FC = () => {
return ( return (
<GapBox> <GapBox>
<div>{userInfo.login_name}</div> {/* 用户信息 Dropdown */}
<Dropdown
trigger={['click']}
placement='bottomRight'
arrow
menu={{
items: [
{
key: 'user-info',
disabled: true, // 只展示,不可操作
label: (
<Space size={8} style={{ color: 'rgba(0,0,0,0.88)' }}>
<span>{userInfo.username}</span>
<span>({userInfo.nickname})</span>
</Space>
),
},
],
}}
>
<div style={{ cursor: 'pointer' }}>
<Space size={4}>
<span></span>
<span
style={{
maxWidth: 120,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
}}
>
{userInfo.username}
</span>
<DownOutlined
style={{
fontSize: 10,
marginTop: 2,
color: '#666',
}}
/>
</Space>
</div>
</Dropdown>
{/* 退出登录 */}
<Popconfirm <Popconfirm
title='确定要退出登录吗?' title='确定要退出登录吗?'
onConfirm={() => { onConfirm={() => {

View File

@@ -19,23 +19,23 @@ interface MenuDataItem {
const userMenu: MenuDataItem = { const userMenu: MenuDataItem = {
name: '用户管理', name: '用户管理',
icon: <UserOutlined style={iconStyle} />, icon: <UserOutlined style={iconStyle} />,
children: [{ name: '用户信息', path: '/user/list', auth: '' }], //SF_ERP_USER_VIEW children: [{ name: '用户信息', path: '/user/list', auth: 'SF_ADMIN_USER_VIEW' }],
}; };
const companyMenu: MenuDataItem = { const companyMenu: MenuDataItem = {
name: '企业管理', name: '企业管理',
icon: <TeamOutlined style={iconStyle} />, icon: <TeamOutlined style={iconStyle} />,
children: [{ name: '企业信息', path: '/company/list', auth: '' }], //SF_ERP_COMPANY_VIEW children: [{ name: '企业信息', path: '/company/list', auth: 'SF_ADMIN_COMPANY_VIEW' }],
}; };
const authMenu: MenuDataItem = { const authMenu: MenuDataItem = {
name: '权限管理', name: '权限管理',
icon: <BarsOutlined style={iconStyle} />, icon: <BarsOutlined style={iconStyle} />,
children: [ children: [
{ name: '管理员信息', path: '/staff/list', auth: '' }, //SF_ERP_ADMIN_VIEW { name: '管理员信息', path: '/staff/list', auth: 'SF_ADMIN_ADMIN_VIEW' },
{ name: '组织架构', path: '/staff/dep', auth: '' }, //SF_ERP_DEPART_VIEW { name: '组织架构', path: '/staff/dep', auth: 'SF_ADMIN_DEPART_VIEW' },
{ name: '岗位角色', path: '/staff/group', auth: '' }, //SF_ERP_GROUP_VIEW { name: '岗位角色', path: '/staff/group', auth: 'SF_ADMIN_GROUP_VIEW' },
{ name: '我的权限', path: '/staff/my', auth: '' }, //SF_MY_RIGHT_VIEW { name: '我的权限', path: '/staff/auth', auth: 'SF_ADMIN_AUTH_VIEW' },
{ name: '登录日志', path: '/staff/login', auth: '' }, //SF_LOGIN_LOG_VIEW { name: '登录日志', path: '/staff/login', auth: 'SF_LOGIN_LOG_VIEW' },
{ name: '操作日志', path: '/staff/sys', auth: '' }, //SF_OPERATE_LOG_VIEW { name: '操作日志', path: '/staff/sys', auth: 'SF_OPERATE_LOG_VIEW' },
], ],
}; };

View File

@@ -6,6 +6,7 @@ import ErrorPage from '@/pages/Error';
import Index from '@/pages/Index'; import Index from '@/pages/Index';
import Login from '@/pages/Record/Login'; import Login from '@/pages/Record/Login';
import Sys from '@/pages/Record/Sys'; import Sys from '@/pages/Record/Sys';
import Auth from '@/pages/Staff/Auth';
import Dep from '@/pages/Staff/Dep'; import Dep from '@/pages/Staff/Dep';
import Grp from '@/pages/Staff/Grp'; import Grp from '@/pages/Staff/Grp';
import StaffList from '@/pages/Staff/List'; import StaffList from '@/pages/Staff/List';
@@ -45,6 +46,7 @@ export const routes: IRouteItem[] = [
{ path: '/list', Component: StaffList }, { path: '/list', Component: StaffList },
{ path: '/dep', Component: Dep }, { path: '/dep', Component: Dep },
{ path: '/group', Component: Grp }, { path: '/group', Component: Grp },
{ path: '/auth', Component: Auth },
{ path: '/login', Component: Login }, { path: '/login', Component: Login },
{ path: '/sys', Component: Sys }, { path: '/sys', Component: Sys },
], ],

View File

@@ -25,10 +25,9 @@ const AppLayout = () => {
useEffect(() => { useEffect(() => {
setLoading(true); setLoading(true);
loginState().then((res) => { loginState().then((res) => {
// console.log(res); userStore.updateUser(toObject(res.admin));
userStore.updateUser(toObject(res.user_info)); authStore.updateAuth(toObject(res.auth) as any);
authStore.updateAuth(toObject(res.auth_info) as any); localStorage.setItem('admin_id', res?.admin?.admin_id);
localStorage.setItem('admin_user_id', res?.user_info?.user_id);
setLoading(false); setLoading(false);
}); });
}, []); }, []);

View File

@@ -105,7 +105,6 @@ const CompanyListForm: React.FC = () => {
function page(curr: number) { function page(curr: number) {
if (!userLoading) { if (!userLoading) {
params.curr_page = curr; params.curr_page = curr;
console.log(params);
userRequest(stringify(params)); userRequest(stringify(params));
} }
} }

View File

@@ -5,21 +5,14 @@ import loginBg from '@/assets/loginBg.jpg';
import { FormItemPlugin, FormPlugin } from '@/components/FormPlugin'; import { FormItemPlugin, FormPlugin } from '@/components/FormPlugin';
import { DefaultERPName } from '@/configs/config'; import { DefaultERPName } from '@/configs/config';
import { AdminServices } from '@/services/AdminServices'; import { AdminServices } from '@/services/AdminServices';
import { type IAuth, useAuthStore } from '@/store/AuthStore';
import { useUserStore } from '@/store/UserStore';
import { toObject } from '@/utils/common';
import { notificationEventBus } from '@/utils/EventBus'; import { notificationEventBus } from '@/utils/EventBus';
import { useRequest } from '@/utils/useRequest'; import { useRequest } from '@/utils/useRequest';
const Login = () => { const Login = () => {
const user = useUserStore();
const auth = useAuthStore();
const [userInfo, setUserInfo] = useState({ username: 'admin', password: '123456', login_type: 1 }); const [userInfo, setUserInfo] = useState({ username: 'admin', password: '123456', login_type: 1 });
const { request, loading } = useRequest(AdminServices.login, { const { request, loading } = useRequest(AdminServices.login, {
onSuccessCodeZero: (res) => { onSuccessCodeZero: () => {
notificationEventBus.emit({ description: '登录成功' }); notificationEventBus.emit({ description: '登录成功' });
user.updateUser(res.admin);
auth.updateAuth(toObject(res.auth) as IAuth);
location.href = '#/'; location.href = '#/';
}, },
}); });

View File

@@ -0,0 +1,137 @@
import { Tree } from 'antd';
import type React from 'react';
import { useEffect, useState } from 'react';
import PageContainerPlugin from '@/components/PageContainer/PageContainerPlugin';
import { AdminServices } from '@/services/AdminServices';
import { useAuthStore } from '@/store/AuthStore';
import { useRequest } from '@/utils/useRequest';
/** 登录管理员权限列表页面 */
const AuthForm: React.FC = () => {
const auth = useAuthStore().auth;
const [menuTree, setMenuTree] = useState<any[]>([]);
const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]);
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
/** 将后端数据转换为 Tree 节点 */
const transformToTreeNodes = (menus: any[]): any[] => {
return menus.map((menu) => {
const childrenNodes: any[] = [];
// 子菜单
if (menu.children?.length) {
menu.children.forEach((child: any) => {
const childNodes: any[] = [];
// 子菜单下的子菜单功能
if (child.children?.length) {
child.children.forEach((func: any) => {
childNodes.push({
title: func.function_ch_name,
key: `func_${func.function_id}`,
isLeaf: true,
});
});
}
// 子菜单自己的功能
if (child.functions?.length) {
child.functions.forEach((func: any) => {
childNodes.push({
title: func.function_ch_name,
key: `func_${func.function_id}`,
isLeaf: true,
});
});
}
childrenNodes.push({
title: child.menu_ch_name,
key: `menu_${child.menu_id}`,
children: childNodes.length ? childNodes : undefined,
});
});
}
// 顶级菜单功能
if (menu.functions?.length) {
menu.functions.forEach((func: any) => {
childrenNodes.push({
title: func.function_ch_name,
key: `func_${func.function_id}`,
isLeaf: true,
});
});
}
return {
title: menu.menu_ch_name,
key: `menu_${menu.menu_id}`,
children: childrenNodes.length ? childrenNodes : undefined,
};
});
};
/** 获取 Tree 所有 key用于默认全展开 */
const getAllTreeKeys = (nodes: any[]): React.Key[] => {
const keys: React.Key[] = [];
const loop = (list: any[]) => {
list.forEach((item) => {
keys.push(item.key);
if (item.children) {
loop(item.children);
}
});
};
loop(nodes);
return keys;
};
/** 获取当前管理员权限 */
const { loading: listLoading, request: listRequest } = useRequest(AdminServices.auth, {
onSuccessCodeZero: (res) => {
const tree = transformToTreeNodes(res.rule);
setMenuTree(tree);
setExpandedKeys(getAllTreeKeys(tree)); // ⭐ 关键:全展开
// 如果后端返回的是 function_id 列表,这里可以顺便做回显
if (res?.auth) {
//const keys = res.auth.split(',').map((id: string) => `func_${id}`);
const keys = res.auth.map((id: number) => `func_${id}`);
setCheckedKeys(keys);
} else {
setCheckedKeys([]);
}
},
});
useEffect(() => {
if (!listLoading) {
listRequest();
}
}, []);
return (
<Tree
checkable
checkStrictly={false} // 父子联动
treeData={menuTree}
checkedKeys={checkedKeys}
expandedKeys={expandedKeys}
onExpand={(keys) => setExpandedKeys(keys)}
onCheck={() => {}} // 只读
/>
);
};
const Auth = () => (
<PageContainerPlugin breadcrumb={['权限管理', '我的权限']}>
<AuthForm />
</PageContainerPlugin>
);
export default Auth;

View File

@@ -61,6 +61,7 @@ const GrpListForm: React.FC = () => {
<GapBox> <GapBox>
{item?.group_id != 1 && ( {item?.group_id != 1 && (
<> <>
{auth.SF_ADMIN_GROUP_EDIT && (
<Button <Button
type='primary' type='primary'
onClick={() => { onClick={() => {
@@ -69,8 +70,10 @@ const GrpListForm: React.FC = () => {
> >
</Button> </Button>
)}
{auth.SF_ADMIN_GROUP_DEL && (
<Popconfirm <Popconfirm
placement='bottom' placement='top'
title='确认删除该岗位角色吗?' title='确认删除该岗位角色吗?'
onConfirm={() => { onConfirm={() => {
deleteAdminGroupRequest(stringify({ group_id: item.group_id })); deleteAdminGroupRequest(stringify({ group_id: item.group_id }));
@@ -78,6 +81,7 @@ const GrpListForm: React.FC = () => {
> >
<Button danger></Button> <Button danger></Button>
</Popconfirm> </Popconfirm>
)}
</> </>
)} )}
</GapBox> </GapBox>
@@ -190,6 +194,7 @@ const GrpListForm: React.FC = () => {
<GapBox style={{ marginBottom: 12, justifyContent: 'space-between' }}> <GapBox style={{ marginBottom: 12, justifyContent: 'space-between' }}>
<GapBox> <GapBox>
{auth.SF_ADMIN_GROUP_ADD && (
<Button <Button
type='primary' type='primary'
onClick={() => { onClick={() => {
@@ -198,6 +203,7 @@ const GrpListForm: React.FC = () => {
> >
</Button> </Button>
)}
</GapBox> </GapBox>
<HeaderPagination <HeaderPagination
style={{ marginBottom: 0, marginRight: 12 }} style={{ marginBottom: 0, marginRight: 12 }}

View File

@@ -67,6 +67,7 @@ const AdminListForm: React.FC = () => {
<GapBox> <GapBox>
{item?.admin_id != 1 && ( {item?.admin_id != 1 && (
<> <>
{auth.SF_ADMIN_ADMIN_EDIT && (
<Button <Button
type='primary' type='primary'
onClick={() => { onClick={() => {
@@ -75,8 +76,10 @@ const AdminListForm: React.FC = () => {
> >
</Button> </Button>
)}
{auth.SF_ADMIN_ADMIN_DEL && (
<Popconfirm <Popconfirm
placement='bottom' placement='top'
title='确认删除该管理员吗?' title='确认删除该管理员吗?'
onConfirm={() => { onConfirm={() => {
deleteAdminRequest(stringify({ admin_id: item.admin_id })); deleteAdminRequest(stringify({ admin_id: item.admin_id }));
@@ -84,6 +87,7 @@ const AdminListForm: React.FC = () => {
> >
<Button danger></Button> <Button danger></Button>
</Popconfirm> </Popconfirm>
)}
</> </>
)} )}
</GapBox> </GapBox>
@@ -107,7 +111,6 @@ const AdminListForm: React.FC = () => {
function page(curr: number) { function page(curr: number) {
if (!listLoading) { if (!listLoading) {
params.curr_page = curr; params.curr_page = curr;
console.log(params);
listRequest(stringify(params)); listRequest(stringify(params));
} }
} }
@@ -219,6 +222,7 @@ const AdminListForm: React.FC = () => {
<GapBox style={{ marginBottom: 12, justifyContent: 'space-between' }}> <GapBox style={{ marginBottom: 12, justifyContent: 'space-between' }}>
<GapBox> <GapBox>
{auth.SF_ADMIN_ADMIN_ADD && (
<Button <Button
type='primary' type='primary'
onClick={() => { onClick={() => {
@@ -227,6 +231,7 @@ const AdminListForm: React.FC = () => {
> >
</Button> </Button>
)}
</GapBox> </GapBox>
<HeaderPagination <HeaderPagination
style={{ marginBottom: 0, marginRight: 12 }} style={{ marginBottom: 0, marginRight: 12 }}

View File

@@ -63,6 +63,7 @@ const DepListForm: React.FC = () => {
<GapBox> <GapBox>
{item?.admin_id != 1 && ( {item?.admin_id != 1 && (
<> <>
{auth.SF_ADMIN_DEPART_EDIT && (
<Button <Button
type='primary' type='primary'
onClick={() => { onClick={() => {
@@ -71,8 +72,10 @@ const DepListForm: React.FC = () => {
> >
</Button> </Button>
)}
{auth.SF_ADMIN_DEPART_DEL && (
<Popconfirm <Popconfirm
placement='bottom' placement='top'
title='确认删除该组织架构吗?' title='确认删除该组织架构吗?'
onConfirm={() => { onConfirm={() => {
deleteAdminDepartmentRequest(stringify({ department_id: item.department_id })); deleteAdminDepartmentRequest(stringify({ department_id: item.department_id }));
@@ -80,6 +83,7 @@ const DepListForm: React.FC = () => {
> >
<Button danger></Button> <Button danger></Button>
</Popconfirm> </Popconfirm>
)}
</> </>
)} )}
</GapBox> </GapBox>
@@ -99,7 +103,6 @@ const DepListForm: React.FC = () => {
function page(curr: number) { function page(curr: number) {
if (!listLoading) { if (!listLoading) {
params.curr_page = curr; params.curr_page = curr;
console.log(params);
listRequest(stringify(params)); listRequest(stringify(params));
} }
} }
@@ -193,6 +196,7 @@ const DepListForm: React.FC = () => {
<GapBox style={{ marginBottom: 12, justifyContent: 'space-between' }}> <GapBox style={{ marginBottom: 12, justifyContent: 'space-between' }}>
<GapBox> <GapBox>
{auth.SF_ADMIN_DEPART_ADD && (
<Button <Button
type='primary' type='primary'
onClick={() => { onClick={() => {
@@ -201,6 +205,7 @@ const DepListForm: React.FC = () => {
> >
</Button> </Button>
)}
</GapBox> </GapBox>
<HeaderPagination <HeaderPagination
style={{ marginBottom: 0, marginRight: 12 }} style={{ marginBottom: 0, marginRight: 12 }}

View File

@@ -16,6 +16,7 @@ import CompanySelect from '@/pages/Company/List/components/CompanySelect';
import { type IUserEditModalType, UserEditModal } from '@/pages/User/List/components/UserEditModal'; import { type IUserEditModalType, UserEditModal } from '@/pages/User/List/components/UserEditModal';
import { UserServices } from '@/services/UserServices'; import { UserServices } from '@/services/UserServices';
import { useAuthStore } from '@/store/AuthStore'; import { useAuthStore } from '@/store/AuthStore';
import { useUserStore } from '@/store/UserStore';
import { tableFixedByPhone, toArray } from '@/utils/common'; import { tableFixedByPhone, toArray } from '@/utils/common';
import { useRequest } from '@/utils/useRequest'; import { useRequest } from '@/utils/useRequest';
@@ -34,8 +35,10 @@ type IParams = IParamsBase & {
/** 用户列表页面 */ /** 用户列表页面 */
const UserListForm: React.FC = () => { const UserListForm: React.FC = () => {
const user = useUserStore().user;
console.log('user', user);
const auth = useAuthStore().auth; const auth = useAuthStore().auth;
console.log('auth', auth);
const [ajaxData, setAjaxData] = useState<IAjaxData>({ count: 0, data: [] }); const [ajaxData, setAjaxData] = useState<IAjaxData>({ count: 0, data: [] });
const [showMoreSearch, setShowMoreSearch] = useState(false); const [showMoreSearch, setShowMoreSearch] = useState(false);
const UserEditModalRef = useRef<IUserEditModalType>(null); const UserEditModalRef = useRef<IUserEditModalType>(null);
@@ -59,15 +62,7 @@ const UserListForm: React.FC = () => {
fixed: tableFixedByPhone('left'), fixed: tableFixedByPhone('left'),
render: (_, item) => ( render: (_, item) => (
<GapBox> <GapBox>
<Button {auth.SF_ADMIN_USER_EDIT && (
type='primary'
onClick={() => {
UserEditModalRef.current?.show(item);
}}
>
</Button>
{auth.SF_ERP_USER_EDIT && (
<Button <Button
type='primary' type='primary'
onClick={() => { onClick={() => {
@@ -80,10 +75,10 @@ const UserListForm: React.FC = () => {
</GapBox> </GapBox>
), ),
}, },
{ title: '用户名', dataIndex: 'login_name', width: 120 },
{ title: '企业', dataIndex: 'company_name', width: 120 }, { title: '企业', dataIndex: 'company_name', width: 120 },
{ title: '类型', dataIndex: 'staff_type', width: 60, render: (value) => staffType[value] }, { title: '用户名', dataIndex: 'login_name', width: 120 },
{ title: '昵称', dataIndex: 'nick_name', width: 120 }, { title: '昵称', dataIndex: 'nick_name', width: 120 },
{ title: '类型', dataIndex: 'staff_type', width: 60, render: (value) => staffType[value] },
{ title: '手机', dataIndex: 'user_phone', width: 120 }, { title: '手机', dataIndex: 'user_phone', width: 120 },
{ title: '性别', dataIndex: 'user_sex', width: 60, render: (value) => userSex[value] }, { title: '性别', dataIndex: 'user_sex', width: 60, render: (value) => userSex[value] },
{ title: '状态', dataIndex: 'state', width: 60, ellipsis: true, render: (value) => userState[value] }, { title: '状态', dataIndex: 'state', width: 60, ellipsis: true, render: (value) => userState[value] },
@@ -103,7 +98,6 @@ const UserListForm: React.FC = () => {
function page(curr: number) { function page(curr: number) {
if (!userLoading) { if (!userLoading) {
params.curr_page = curr; params.curr_page = curr;
console.log(params);
userRequest(stringify(params)); userRequest(stringify(params));
} }
} }
@@ -209,6 +203,7 @@ const UserListForm: React.FC = () => {
<GapBox style={{ marginBottom: 12, justifyContent: 'space-between' }}> <GapBox style={{ marginBottom: 12, justifyContent: 'space-between' }}>
<GapBox> <GapBox>
{auth.SF_ADMIN_USER_ADD && (
<Button <Button
type='primary' type='primary'
onClick={() => { onClick={() => {
@@ -217,6 +212,7 @@ const UserListForm: React.FC = () => {
> >
</Button> </Button>
)}
</GapBox> </GapBox>
<HeaderPagination <HeaderPagination
style={{ marginBottom: 0, marginRight: 12 }} style={{ marginBottom: 0, marginRight: 12 }}

View File

@@ -10,4 +10,6 @@ export const AdminServices = {
add: '/Admin/add', add: '/Admin/add',
edit: '/Admin/edit', edit: '/Admin/edit',
del: '/Admin/del', del: '/Admin/del',
/** 登录管理员权限列表 */
auth: '/Admin/auth',
} as const; } as const;

View File

@@ -1,19 +1,16 @@
export type UserInfo = { export type UserInfo = {
login_name?: string; admin_id?: number;
logo?: string; username?: string;
last_login_time?: string; nickname?: string;
nick_name?: string; avatar?: string;
state?: number; status?: number;
user_id?: number; email?: string;
user_sex?: number; mobile?: string;
user_phone?: string;
visit_times?: string;
create_date?: string; create_date?: string;
logoKey?: number;
wx_head_img?: string; wx_head_img?: string;
wx_nick_name?: string; wx_nick_name?: string;
wx_open_id?: string; wx_open_id?: string;
self_account?: number; department_id?: number;
}; };
export type CompanyInfo = { export type CompanyInfo = {