diff --git a/src/components/Header/HeaderUserInfo.tsx b/src/components/Header/HeaderUserInfo.tsx
index b0ead18..6ffcd7f 100644
--- a/src/components/Header/HeaderUserInfo.tsx
+++ b/src/components/Header/HeaderUserInfo.tsx
@@ -1,5 +1,6 @@
-import { DownOutlined } from '@ant-design/icons';
+import { DownOutlined, SettingOutlined } from '@ant-design/icons';
import { Button, Dropdown, Popconfirm, Space } from 'antd';
+import { navigate } from '@/router/routerUtils';
import { useUserStore } from '@/store/UserStore';
import { GapBox } from '../GapBox';
@@ -16,7 +17,7 @@ export const HeaderUserInfo: React.FC = () => {
menu={{
items: [
{
- key: 'user-info',
+ key: 'admin-info',
disabled: true, // 只展示,不可操作
label: (
@@ -26,6 +27,15 @@ export const HeaderUserInfo: React.FC = () => {
),
},
+ {
+ type: 'divider',
+ },
+ {
+ key: 'profile',
+ icon: ,
+ label: '个人信息',
+ onClick: () => navigate('/profile'),
+ },
],
}}
>
diff --git a/src/components/TabNavPlugin/TabNavSaveCheckBoxPlugin.tsx b/src/components/TabNavPlugin/TabNavSaveCheckBoxPlugin.tsx
index 0341f11..86a4e18 100644
--- a/src/components/TabNavPlugin/TabNavSaveCheckBoxPlugin.tsx
+++ b/src/components/TabNavPlugin/TabNavSaveCheckBoxPlugin.tsx
@@ -1,13 +1,11 @@
import { Checkbox } from 'antd';
import { useEffect, useState } from 'react';
-import { useCompanyStore } from '@/store/CompanyStore';
import { useUserStore } from '@/store/UserStore';
const TabNavSaveCheckBoxPlugin = () => {
const [value, setValue] = useState(false);
const user = useUserStore().user;
- const company = useCompanyStore().company;
- const storageKey = `u${user.user_id}_c${company.company_id}_tabNavSave`;
+ const storageKey = `u${user.admin_id}_tabNavSave`;
useEffect(() => {
setValue(localStorage.getItem(storageKey) == '1');
}, []);
@@ -19,7 +17,8 @@ const TabNavSaveCheckBoxPlugin = () => {
console.log(e);
setValue(e.target.checked);
localStorage.setItem(storageKey, `${e.target.checked ? 1 : 0}`);
- }}>
+ }}
+ >
保留标签
);
diff --git a/src/components/TabNavPlugin/index.tsx b/src/components/TabNavPlugin/index.tsx
index e7c6d86..2bef747 100644
--- a/src/components/TabNavPlugin/index.tsx
+++ b/src/components/TabNavPlugin/index.tsx
@@ -3,6 +3,7 @@ import { Menu } from 'antd';
import type React from 'react';
import { useEffect, useRef, useState } from 'react';
import { Colors, DefaultERPName, headerHeight } from '@/configs/config';
+import { routes } from '@/configs/routes';
import { getHash } from '@/router/routerUtils';
import { useCompanyStore } from '@/store/CompanyStore';
import { useRefreshStore } from '@/store/RefreshStore';
@@ -27,6 +28,29 @@ const TabNavPlugin: React.FC = () => {
const modeRef = useRef('');
const company = useCompanyStore().company;
+ function findRouteTitle(pathname: string, routes: any[], parentPath = ''): string | undefined {
+ for (const route of routes) {
+ const routePath = route.path || '';
+ //const fullPath = routePath.startsWith('/') ? routePath : (parentPath + '/' + routePath).replace(/\/+/g, '/');
+ const fullPath = routePath.startsWith('/') ? routePath : `${parentPath}/${routePath}`.replace(/\/+/g, '/');
+
+ // 如果 pathname 完全匹配当前路由,返回 title
+ if (route.title && pathname === fullPath) return route.title;
+
+ // 如果有子路由,递归查找
+ if (route.children?.length) {
+ const title = findRouteTitle(pathname, route.children, fullPath);
+ if (title) return title;
+
+ // 如果 pathname === 父路由 path 且子路由存在 title,返回第一个子路由的 title
+ if (pathname === fullPath && route.children[0]?.title) {
+ return route.children[0].title;
+ }
+ }
+ }
+ return undefined;
+ }
+
const callback = () => {
const span = document.querySelector('.urlList-active');
if (urlListRef.current && span && modeRef.current != 'del') {
@@ -102,6 +126,13 @@ const TabNavPlugin: React.FC = () => {
const pathname = getHash();
setPathname(pathname);
+ const routeTitle = findRouteTitle(pathname, routes);
+ // 决定最终 title
+ const finalTitle =
+ routeTitle || (document.title ? `${document.title}`.replace(` - ${DefaultERPName}`, '') : pathname);
+
+ document.title = `${finalTitle} - ${DefaultERPName}`;
+
let flag = true;
if ((document.title == '404' || pathname == '/temp' || pathname == '/') && !isPhone) {
return;
@@ -117,7 +148,7 @@ const TabNavPlugin: React.FC = () => {
if (flag) {
urlListRef.current.push({
url: pathname,
- title: `${document.title}`.replace(` - ${DefaultERPName}`, ''),
+ title: finalTitle,
search: '',
});
setUrlList([...urlListRef.current]);
diff --git a/src/configs/routes.ts b/src/configs/routes.ts
index 026ca19..a04c4b3 100644
--- a/src/configs/routes.ts
+++ b/src/configs/routes.ts
@@ -59,5 +59,13 @@ export const routes: IRouteItem[] = [
//
],
},
+ {
+ path: '/profile',
+ Layout: AppLayout,
+ children: [
+ { path: '/index', Component: lazy(() => import('@/pages/Profile')), title: '个人信息' },
+ //
+ ],
+ },
{ path: '*', Layout: ErrorPage, children: [] },
];
diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx
index 8d58320..543ccb1 100644
--- a/src/pages/Login/index.tsx
+++ b/src/pages/Login/index.tsx
@@ -17,7 +17,7 @@ const Login = () => {
},
});
- document.title = '登录';
+ document.title = `登录 - ${DefaultERPName}`;
const login = () => {
request(stringify(userInfo));
@@ -48,7 +48,6 @@ const Login = () => {
>
{DefaultERPName}
- {document.title}
diff --git a/src/pages/Profile/components/LoginLog.tsx b/src/pages/Profile/components/LoginLog.tsx
new file mode 100644
index 0000000..b63dbc7
--- /dev/null
+++ b/src/pages/Profile/components/LoginLog.tsx
@@ -0,0 +1,101 @@
+import { stringify } from 'qs';
+import { useEffect, useState } from 'react';
+import { FooterPagination } from '@/components/PaginationPlugin';
+import type { ColumnsTypeUltra } from '@/components/TableColumnsFilterPlugin';
+import { TablePlugin } from '@/components/TablePlugin';
+import { statusObj } from '@/configs/adminLogConfig';
+import type { IAjaxDataBase, IParamsBase } from '@/interfaces/common';
+import { AdminLogServices } from '@/services/AdminLogServices';
+import { tableFixedByPhone, toArray } from '@/utils/common';
+import { useRequest } from '@/utils/useRequest';
+
+interface IAjaxData extends IAjaxDataBase {
+ data: any[];
+}
+type IParams = IParamsBase & {
+ login_dateL?: string;
+ login_dateU?: string;
+ admin_id?: number;
+};
+
+/** 登录日志页面 */
+const AdminLoginLogForm: React.FC = () => {
+ const [params] = useState({
+ curr_page: 1,
+ page_count: 20,
+ admin_id: Number(localStorage.getItem('admin_id')) || undefined,
+ });
+ const [ajaxData, setAjaxData] = useState({ count: 0, data: [] });
+
+ const { loading: listLoading, request: listRequest } = useRequest(AdminLogServices.getAdminLoginLogAjaxList, {
+ onSuccessCodeZero: (res) => {
+ setAjaxData({
+ count: res.count || 0,
+ data: toArray(res.data),
+ });
+ },
+ });
+
+ const columns: ColumnsTypeUltra = [
+ {
+ title: '#',
+ width: 50,
+ fixed: tableFixedByPhone('left'),
+ align: 'center',
+ render(_value, _record, index) {
+ return index + 1;
+ },
+ },
+ { title: '登录账号', dataIndex: 'username', width: 80 },
+ { title: '登录IP', dataIndex: 'ip', width: 100 },
+ { title: '登录地址', dataIndex: 'ip_location', width: 100 },
+ { title: '平台', dataIndex: 'os', width: 80 },
+ { title: '浏览器', dataIndex: 'browser', width: 80 },
+ { title: '登录时间', dataIndex: 'login_date', width: 120 },
+ { title: '状态', dataIndex: 'status', width: 60, render: (value) => statusObj[value] },
+ {
+ title: '备注',
+ dataIndex: 'remark',
+ width: 120,
+ },
+ ];
+
+ function page(curr: number) {
+ if (!listLoading) {
+ params.curr_page = curr;
+ listRequest(stringify(params));
+ }
+ }
+
+ useEffect(() => {
+ page(1);
+ }, []);
+
+ return (
+ <>
+ {
+ page(1);
+ }}
+ dataSource={ajaxData.data}
+ columns={columns}
+ scroll={{ x: true }}
+ rowKey={'login_id'}
+ />
+
+ {
+ params.page_count = pageSize;
+ page(curr);
+ }}
+ />
+ >
+ );
+};
+
+/** 登录日志页面 */
+export default AdminLoginLogForm;
diff --git a/src/pages/Profile/components/OperateLog.tsx b/src/pages/Profile/components/OperateLog.tsx
new file mode 100644
index 0000000..03c4ec0
--- /dev/null
+++ b/src/pages/Profile/components/OperateLog.tsx
@@ -0,0 +1,95 @@
+import { stringify } from 'qs';
+import { useEffect, useState } from 'react';
+import { FooterPagination } from '@/components/PaginationPlugin';
+import type { ColumnsTypeUltra } from '@/components/TableColumnsFilterPlugin';
+import { TablePlugin } from '@/components/TablePlugin';
+import { statusObj } from '@/configs/adminLogConfig';
+import type { IAjaxDataBase, IParamsBase } from '@/interfaces/common';
+import { AdminLogServices } from '@/services/AdminLogServices';
+import { tableFixedByPhone, toArray } from '@/utils/common';
+import { useRequest } from '@/utils/useRequest';
+
+interface IAjaxData extends IAjaxDataBase {
+ data: any[];
+}
+type IParams = IParamsBase & {
+ create_dateL?: string;
+ create_dateU?: string;
+ admin_id?: number;
+};
+
+/** 操作日志页面 */
+const AdminSysLogForm: React.FC = () => {
+ const [params] = useState({
+ curr_page: 1,
+ page_count: 20,
+ admin_id: Number(localStorage.getItem('admin_id')) || undefined,
+ });
+ const [ajaxData, setAjaxData] = useState({ count: 0, data: [] });
+
+ const { loading: listLoading, request: listRequest } = useRequest(AdminLogServices.getAdminSysLogAjaxList, {
+ onSuccessCodeZero: (res) => {
+ setAjaxData({
+ count: res.count || 0,
+ data: toArray(res.data),
+ });
+ },
+ });
+
+ const columns: ColumnsTypeUltra = [
+ {
+ title: '#',
+ width: 20,
+ fixed: tableFixedByPhone('left'),
+ align: 'center',
+ render(_value, _record, index) {
+ return index + 1;
+ },
+ },
+ { title: '操作账号', dataIndex: 'username', width: 50 },
+ { title: '操作IP', dataIndex: 'ip', width: 100 },
+ { title: '菜单', dataIndex: 'menu_ch_name', width: 80 },
+ { title: '权限', dataIndex: 'function_ch_name', width: 60 },
+ { title: '操作时间', dataIndex: 'create_date', width: 120 },
+ { title: '状态', dataIndex: 'status', width: 60, render: (value) => statusObj[value] },
+ ];
+
+ function page(curr: number) {
+ if (!listLoading) {
+ params.curr_page = curr;
+ listRequest(stringify(params));
+ }
+ }
+
+ useEffect(() => {
+ page(1);
+ }, []);
+
+ return (
+ <>
+ {
+ page(1);
+ }}
+ dataSource={ajaxData.data}
+ columns={columns}
+ scroll={{ x: true }}
+ rowKey={'sys_id'}
+ />
+
+ {
+ params.page_count = pageSize;
+ page(curr);
+ }}
+ />
+ >
+ );
+};
+
+/** 操作日志页面 */
+export default AdminSysLogForm;
diff --git a/src/pages/Profile/components/ProfileEditForm.tsx b/src/pages/Profile/components/ProfileEditForm.tsx
new file mode 100644
index 0000000..5a4fbc6
--- /dev/null
+++ b/src/pages/Profile/components/ProfileEditForm.tsx
@@ -0,0 +1,96 @@
+import { Button, Form, Input, notification } from 'antd';
+import { stringify } from 'qs';
+import type React from 'react';
+import { useEffect } from 'react';
+import { AdminServices } from '@/services/AdminServices';
+import { useUserStore } from '@/store/UserStore';
+import type { IRef } from '@/utils/type';
+import { useRequest } from '@/utils/useRequest';
+
+interface IProps extends IRef {
+ onCallback?: () => void;
+}
+
+export type IProfileEditFormType = {
+ show: (data?: any) => void;
+};
+
+export const ProfileEditForm: React.FC = (props) => {
+ const [form] = Form.useForm();
+ const userInfo = useUserStore().user;
+
+ // 请求成功回调
+ const success = (res: any) => {
+ if (res.err_code === 0) {
+ notification.success({ message: '保存成功' });
+ props.onCallback?.();
+ }
+ };
+
+ const { loading: editLoading, request: editRequest } = useRequest(AdminServices.profile, { onSuccess: success });
+
+ const save = async () => {
+ try {
+ const values = await form.validateFields();
+ editRequest(stringify(values));
+ } catch (error) {
+ console.log('表单验证未通过', error);
+ }
+ };
+
+ useEffect(() => {
+ form.setFieldsValue({
+ username: userInfo.username,
+ mobile: userInfo.mobile,
+ email: userInfo.email,
+ nickname: userInfo.nickname,
+ });
+ }, [userInfo]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/pages/Profile/index.tsx b/src/pages/Profile/index.tsx
new file mode 100644
index 0000000..04374ea
--- /dev/null
+++ b/src/pages/Profile/index.tsx
@@ -0,0 +1,69 @@
+import { Card, Col, Row, Tabs } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+import PageContainerPlugin from '@/components/PageContainer/PageContainerPlugin';
+import LoginLog from './components/LoginLog';
+import OperateLog from './components/OperateLog';
+import { type IProfileEditFormType, ProfileEditForm } from './components/ProfileEditForm';
+
+/** 管理员个人信息页面 */
+const ProfileForm: React.FC = () => {
+ const ProfileEditFormRef = useRef(null);
+ const [activeTab, setActiveTab] = useState('login');
+
+ const getTabStorageKey = () => {
+ const admin_id = localStorage.getItem('admin_id') || 'guest';
+ return `log_list_active_tab_${admin_id}`;
+ };
+ const onTabChange = (key: string) => {
+ setActiveTab(key);
+ };
+
+ /** 进入页面时恢复上一次 tab */
+ useEffect(() => {
+ const lastTab = localStorage.getItem(getTabStorageKey());
+ if (lastTab) {
+ setActiveTab(lastTab);
+ }
+ }, []);
+
+ return (
+
+ {/* 左:个人信息编辑 */}
+
+
+
+
+
+
+ {/* 右:日志 */}
+
+
+ ,
+ },
+ {
+ key: 'operate',
+ label: '操作日志',
+ children: ,
+ },
+ ]}
+ />
+
+
+
+ );
+};
+
+const Profile = () => (
+
+
+
+);
+export default Profile;
diff --git a/src/services/AdminLogServices.ts b/src/services/AdminLogServices.ts
index 4e0541a..813476e 100644
--- a/src/services/AdminLogServices.ts
+++ b/src/services/AdminLogServices.ts
@@ -7,4 +7,10 @@ export const AdminLogServices = {
getAdminMenuAjaxList: '/AdminMenu/getAdminMenuAjaxList',
/** 权限 */
getAdminFunctionAjaxList: '/AdminFunction/getAdminFunctionAjaxList',
+
+ /** AJAX */
+ /** 登录日志 */
+ getAdminLoginLogAjaxList: '/AdminLog/getAdminLoginLogAjaxList',
+ /** 操作日志 */
+ getAdminSysLogAjaxList: '/AdminLog/getAdminSysLogAjaxList',
} as const;
diff --git a/src/services/AdminServices.ts b/src/services/AdminServices.ts
index a695544..4ed1ef6 100644
--- a/src/services/AdminServices.ts
+++ b/src/services/AdminServices.ts
@@ -10,6 +10,8 @@ export const AdminServices = {
add: '/Admin/add',
edit: '/Admin/edit',
del: '/Admin/del',
+ /** 个人设置 */
+ profile: '/Admin/profile',
/** 登录管理员权限列表 */
auth: '/Admin/auth',
} as const;