前端重构
This commit is contained in:
parent
f4fcdaa322
commit
cf16ab6448
133
web/src/app.tsx
133
web/src/app.tsx
|
|
@ -3,6 +3,7 @@
|
||||||
* 应用运行时配置
|
* 应用运行时配置
|
||||||
*/
|
*/
|
||||||
import { AvatarDropdown, AvatarName, Footer } from '@/components';
|
import { AvatarDropdown, AvatarName, Footer } from '@/components';
|
||||||
|
import CommandMenu from '@/components/CommandMenu';
|
||||||
import { getUserInfo } from '@/services/kratos/user';
|
import { getUserInfo } from '@/services/kratos/user';
|
||||||
import { getToken, removeToken } from '@/utils/auth';
|
import { getToken, removeToken } from '@/utils/auth';
|
||||||
import {
|
import {
|
||||||
|
|
@ -12,12 +13,14 @@ import {
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
QuestionCircleOutlined,
|
QuestionCircleOutlined,
|
||||||
|
SearchOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import type { Settings as LayoutSettings } from '@ant-design/pro-components';
|
import type { Settings as LayoutSettings } from '@ant-design/pro-components';
|
||||||
import { SettingDrawer } from '@ant-design/pro-components';
|
import { SettingDrawer } from '@ant-design/pro-components';
|
||||||
import type { RequestConfig, RunTimeLayoutConfig } from '@umijs/max';
|
import type { RequestConfig, RunTimeLayoutConfig } from '@umijs/max';
|
||||||
import { history } from '@umijs/max';
|
import { history } from '@umijs/max';
|
||||||
import { message, Tooltip } from 'antd';
|
import { message, Tooltip } from 'antd';
|
||||||
|
import React, { useState } from 'react';
|
||||||
import defaultSettings from '../config/defaultSettings';
|
import defaultSettings from '../config/defaultSettings';
|
||||||
import { errorConfig } from './requestErrorConfig';
|
import { errorConfig } from './requestErrorConfig';
|
||||||
|
|
||||||
|
|
@ -114,69 +117,69 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 打开设置
|
||||||
|
const handleOpenSetting = () => {
|
||||||
|
const settingBtn = document.querySelector('.ant-pro-setting-drawer-handle');
|
||||||
|
if (settingBtn) {
|
||||||
|
(settingBtn as HTMLElement).click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconStyle: React.CSSProperties = {
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: '50%',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
cursor: 'pointer',
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 先展开 settings,让后面的配置可以覆盖
|
// 先展开 settings,让后面的配置可以覆盖
|
||||||
...initialState?.settings,
|
...initialState?.settings,
|
||||||
|
|
||||||
// 右上角操作区
|
// 右上角操作区
|
||||||
actionsRender: () => {
|
actionsRender: () => {
|
||||||
const isDark = initialState?.settings?.navTheme === 'realDark';
|
const isDarkTheme = initialState?.settings?.navTheme === 'realDark';
|
||||||
return [
|
return [
|
||||||
// 帮助文档
|
// 帮助文档
|
||||||
<Tooltip title="帮助文档" key="doc">
|
<Tooltip title="帮助文档" key="doc">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/go-kratos/kratos"
|
href="https://go-kratos.dev/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
style={{
|
style={{ ...iconStyle, color: 'inherit' }}
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
borderRadius: '50%',
|
|
||||||
border: '1px solid #d9d9d9',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'inherit',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<QuestionCircleOutlined style={{ fontSize: 16 }} />
|
<QuestionCircleOutlined style={{ fontSize: 16 }} />
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>,
|
</Tooltip>,
|
||||||
|
// 搜索 (Ctrl+K)
|
||||||
|
<Tooltip title="搜索 (Ctrl+K)" key="search">
|
||||||
|
<span
|
||||||
|
onClick={() => window.dispatchEvent(new CustomEvent('openCommandMenu'))}
|
||||||
|
style={iconStyle}
|
||||||
|
>
|
||||||
|
<SearchOutlined style={{ fontSize: 16 }} />
|
||||||
|
</span>
|
||||||
|
</Tooltip>,
|
||||||
|
// 系统设置
|
||||||
|
<Tooltip title="系统设置" key="setting">
|
||||||
|
<span onClick={handleOpenSetting} style={iconStyle}>
|
||||||
|
<SettingOutlined style={{ fontSize: 16 }} />
|
||||||
|
</span>
|
||||||
|
</Tooltip>,
|
||||||
// 刷新
|
// 刷新
|
||||||
<Tooltip title="刷新" key="refresh">
|
<Tooltip title="刷新" key="refresh">
|
||||||
<span
|
<span onClick={handleRefresh} style={iconStyle}>
|
||||||
onClick={handleRefresh}
|
|
||||||
style={{
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
borderRadius: '50%',
|
|
||||||
border: '1px solid #d9d9d9',
|
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ReloadOutlined style={{ fontSize: 16 }} />
|
<ReloadOutlined style={{ fontSize: 16 }} />
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>,
|
</Tooltip>,
|
||||||
// 切换主题
|
// 切换主题
|
||||||
<Tooltip title="切换主题" key="theme">
|
<Tooltip title="切换主题" key="theme">
|
||||||
<span
|
<span onClick={handleToggleTheme} style={iconStyle}>
|
||||||
onClick={handleToggleTheme}
|
{isDarkTheme ? (
|
||||||
style={{
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
borderRadius: '50%',
|
|
||||||
border: '1px solid #d9d9d9',
|
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isDark ? (
|
|
||||||
<BulbFilled style={{ fontSize: 16, color: '#faad14' }} />
|
<BulbFilled style={{ fontSize: 16, color: '#faad14' }} />
|
||||||
) : (
|
) : (
|
||||||
<BulbOutlined style={{ fontSize: 16 }} />
|
<BulbOutlined style={{ fontSize: 16 }} />
|
||||||
|
|
@ -188,7 +191,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
|
||||||
|
|
||||||
// 头像配置
|
// 头像配置
|
||||||
avatarProps: {
|
avatarProps: {
|
||||||
src: initialState?.currentUser?.avatar || initialState?.currentUser?.headerImg,
|
src: initialState?.currentUser?.avatar || initialState?.currentUser?.headerImg || '/default-avatar.svg',
|
||||||
title: <AvatarName />,
|
title: <AvatarName />,
|
||||||
icon: <UserOutlined />,
|
icon: <UserOutlined />,
|
||||||
render: (_, avatarChildren) => <AvatarDropdown menu>{avatarChildren}</AvatarDropdown>,
|
render: (_, avatarChildren) => <AvatarDropdown menu>{avatarChildren}</AvatarDropdown>,
|
||||||
|
|
@ -231,25 +234,47 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{children}
|
{children}
|
||||||
{isDev && (
|
<CommandMenuWrapper />
|
||||||
<SettingDrawer
|
<SettingDrawer
|
||||||
disableUrlParams
|
disableUrlParams
|
||||||
enableDarkTheme
|
enableDarkTheme
|
||||||
settings={initialState?.settings}
|
settings={initialState?.settings}
|
||||||
onSettingChange={(settings) => {
|
onSettingChange={(settings) => {
|
||||||
setInitialState((preInitialState) => ({
|
setInitialState((preInitialState) => ({
|
||||||
...preInitialState,
|
...preInitialState,
|
||||||
settings,
|
settings,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// CommandMenu包装组件
|
||||||
|
const CommandMenuWrapper: React.FC = () => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleOpen = () => setOpen(true);
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.ctrlKey && e.key === 'k') {
|
||||||
|
e.preventDefault();
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('openCommandMenu', handleOpen);
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('openCommandMenu', handleOpen);
|
||||||
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <CommandMenu open={open} onClose={() => setOpen(false)} />;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求配置
|
* 请求配置
|
||||||
* @see https://umijs.org/docs/max/request#配置
|
* @see https://umijs.org/docs/max/request#配置
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
/**
|
||||||
|
* KRA - Command Menu Component
|
||||||
|
* 快捷搜索菜单,支持Ctrl+K快捷键
|
||||||
|
*/
|
||||||
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
|
import { Modal, Input, List, Typography, Space, Tag } from 'antd';
|
||||||
|
import { SearchOutlined, ArrowRightOutlined, BulbOutlined, LogoutOutlined } from '@ant-design/icons';
|
||||||
|
import { history, useModel } from '@umijs/max';
|
||||||
|
import { createStyles } from 'antd-style';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const useStyles = createStyles(({ token }) => ({
|
||||||
|
searchInput: {
|
||||||
|
fontSize: 16,
|
||||||
|
padding: '12px 16px',
|
||||||
|
},
|
||||||
|
categoryTitle: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: token.colorTextSecondary,
|
||||||
|
marginTop: 8,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
listItem: {
|
||||||
|
padding: '8px 12px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: 4,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: token.colorBgTextHover,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface CommandMenuProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MenuItem {
|
||||||
|
label: string;
|
||||||
|
path?: string;
|
||||||
|
action?: () => void;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MenuCategory {
|
||||||
|
label: string;
|
||||||
|
items: MenuItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommandMenu: React.FC<CommandMenuProps> = ({ open, onClose }) => {
|
||||||
|
const { styles } = useStyles();
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
const { initialState, setInitialState } = useModel('@@initialState');
|
||||||
|
|
||||||
|
// 菜单项
|
||||||
|
const menuCategories: MenuCategory[] = useMemo(() => {
|
||||||
|
const categories: MenuCategory[] = [
|
||||||
|
{
|
||||||
|
label: '跳转',
|
||||||
|
items: [
|
||||||
|
{ label: '仪表盘', path: '/dashboard' },
|
||||||
|
{ label: '用户管理', path: '/admin/user' },
|
||||||
|
{ label: '角色管理', path: '/admin/authority' },
|
||||||
|
{ label: '菜单管理', path: '/admin/menu' },
|
||||||
|
{ label: 'API管理', path: '/admin/api' },
|
||||||
|
{ label: '操作日志', path: '/admin/operation' },
|
||||||
|
{ label: '字典管理', path: '/admin/dictionary' },
|
||||||
|
{ label: '参数配置', path: '/admin/params' },
|
||||||
|
{ label: '系统配置', path: '/systemTools/system' },
|
||||||
|
{ label: '个人信息', path: '/person' },
|
||||||
|
{ label: '关于', path: '/about' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '操作',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: '亮色主题',
|
||||||
|
icon: <BulbOutlined />,
|
||||||
|
action: () => {
|
||||||
|
setInitialState((s) => ({
|
||||||
|
...s,
|
||||||
|
settings: { ...s?.settings, navTheme: 'light' },
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '暗色主题',
|
||||||
|
icon: <BulbOutlined />,
|
||||||
|
action: () => {
|
||||||
|
setInitialState((s) => ({
|
||||||
|
...s,
|
||||||
|
settings: { ...s?.settings, navTheme: 'realDark' },
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '退出登录',
|
||||||
|
icon: <LogoutOutlined />,
|
||||||
|
action: () => {
|
||||||
|
history.push('/user/login');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return categories;
|
||||||
|
}, [setInitialState]);
|
||||||
|
|
||||||
|
// 过滤菜单
|
||||||
|
const filteredCategories = useMemo(() => {
|
||||||
|
if (!searchValue) return menuCategories;
|
||||||
|
return menuCategories
|
||||||
|
.map((category) => ({
|
||||||
|
...category,
|
||||||
|
items: category.items.filter((item) =>
|
||||||
|
item.label.toLowerCase().includes(searchValue.toLowerCase())
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
.filter((category) => category.items.length > 0);
|
||||||
|
}, [menuCategories, searchValue]);
|
||||||
|
|
||||||
|
// 处理点击
|
||||||
|
const handleItemClick = (item: MenuItem) => {
|
||||||
|
if (item.path) {
|
||||||
|
history.push(item.path);
|
||||||
|
} else if (item.action) {
|
||||||
|
item.action();
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
setSearchValue('');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听Ctrl+K快捷键
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.ctrlKey && e.key === 'k') {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onCancel={onClose}
|
||||||
|
footer={null}
|
||||||
|
width={500}
|
||||||
|
closable={false}
|
||||||
|
styles={{ body: { maxHeight: '50vh', overflow: 'auto', padding: '12px' } }}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="请输入你需要快捷到达的功能"
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
value={searchValue}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
className={styles.searchInput}
|
||||||
|
autoFocus
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
{filteredCategories.map((category, index) => (
|
||||||
|
<div key={index}>
|
||||||
|
{category.items.length > 0 && (
|
||||||
|
<>
|
||||||
|
<div className={styles.categoryTitle}>{category.label}</div>
|
||||||
|
<List
|
||||||
|
size="small"
|
||||||
|
dataSource={category.items}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<div
|
||||||
|
className={styles.listItem}
|
||||||
|
onClick={() => handleItemClick(item)}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
{item.icon || <ArrowRightOutlined />}
|
||||||
|
<Text>{item.label}</Text>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CommandMenu;
|
||||||
|
|
@ -134,7 +134,7 @@ const AboutPage: React.FC = () => {
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
{/* 头部信息 */}
|
{/* 头部信息 */}
|
||||||
<Card className={styles.headerCard} bordered={false}>
|
<Card className={styles.headerCard} variant="borderless">
|
||||||
<div className={styles.logoWrapper}>
|
<div className={styles.logoWrapper}>
|
||||||
<Logo size={80} />
|
<Logo size={80} />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -166,7 +166,7 @@ const AboutPage: React.FC = () => {
|
||||||
<Row gutter={[24, 24]}>
|
<Row gutter={[24, 24]}>
|
||||||
{/* 系统信息 */}
|
{/* 系统信息 */}
|
||||||
<Col xs={24} lg={12}>
|
<Col xs={24} lg={12}>
|
||||||
<Card title="系统信息" className={styles.infoCard} bordered={false}>
|
<Card title="系统信息" className={styles.infoCard} variant="borderless">
|
||||||
<Descriptions column={1} labelStyle={{ width: '120px' }}>
|
<Descriptions column={1} labelStyle={{ width: '120px' }}>
|
||||||
<Descriptions.Item label="系统名称">Kratos Admin</Descriptions.Item>
|
<Descriptions.Item label="系统名称">Kratos Admin</Descriptions.Item>
|
||||||
<Descriptions.Item label="当前版本">
|
<Descriptions.Item label="当前版本">
|
||||||
|
|
@ -200,7 +200,7 @@ const AboutPage: React.FC = () => {
|
||||||
|
|
||||||
{/* 技术栈 */}
|
{/* 技术栈 */}
|
||||||
<Col xs={24} lg={12}>
|
<Col xs={24} lg={12}>
|
||||||
<Card title="技术栈" className={styles.infoCard} bordered={false}>
|
<Card title="技术栈" className={styles.infoCard} variant="borderless">
|
||||||
<Descriptions column={1} labelStyle={{ width: '120px' }}>
|
<Descriptions column={1} labelStyle={{ width: '120px' }}>
|
||||||
<Descriptions.Item label="后端语言">Go 1.21+</Descriptions.Item>
|
<Descriptions.Item label="后端语言">Go 1.21+</Descriptions.Item>
|
||||||
<Descriptions.Item label="微服务框架">Kratos v2</Descriptions.Item>
|
<Descriptions.Item label="微服务框架">Kratos v2</Descriptions.Item>
|
||||||
|
|
@ -215,7 +215,7 @@ const AboutPage: React.FC = () => {
|
||||||
|
|
||||||
{/* 核心特性 */}
|
{/* 核心特性 */}
|
||||||
<Col xs={24}>
|
<Col xs={24}>
|
||||||
<Card title="核心特性" className={styles.infoCard} bordered={false}>
|
<Card title="核心特性" className={styles.infoCard} variant="borderless">
|
||||||
<Row gutter={[24, 0]}>
|
<Row gutter={[24, 0]}>
|
||||||
{features.map((feature, index) => (
|
{features.map((feature, index) => (
|
||||||
<Col xs={24} md={12} lg={8} key={index}>
|
<Col xs={24} md={12} lg={8} key={index}>
|
||||||
|
|
@ -239,7 +239,7 @@ const AboutPage: React.FC = () => {
|
||||||
|
|
||||||
{/* 鸣谢 */}
|
{/* 鸣谢 */}
|
||||||
<Col xs={24}>
|
<Col xs={24}>
|
||||||
<Card title="鸣谢" className={styles.infoCard} bordered={false}>
|
<Card title="鸣谢" className={styles.infoCard} variant="borderless">
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
Kratos Admin 的开发离不开以下优秀的开源项目:
|
Kratos Admin 的开发离不开以下优秀的开源项目:
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ const Notice: React.FC = () => {
|
||||||
<Card
|
<Card
|
||||||
title="公告"
|
title="公告"
|
||||||
className="kra-dashboard-card"
|
className="kra-dashboard-card"
|
||||||
bordered={false}
|
variant="borderless"
|
||||||
extra={<a href="#">更多</a>}
|
extra={<a href="#">更多</a>}
|
||||||
>
|
>
|
||||||
<List
|
<List
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ const PluginTable: React.FC = () => {
|
||||||
<Card
|
<Card
|
||||||
title="最新插件"
|
title="最新插件"
|
||||||
className="kra-dashboard-card"
|
className="kra-dashboard-card"
|
||||||
bordered={false}
|
variant="borderless"
|
||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ const QuickLinks: React.FC = () => {
|
||||||
<Card
|
<Card
|
||||||
title="快捷功能"
|
title="快捷功能"
|
||||||
className="kra-dashboard-card"
|
className="kra-dashboard-card"
|
||||||
bordered={false}
|
variant="borderless"
|
||||||
>
|
>
|
||||||
<Row gutter={[12, 12]}>
|
<Row gutter={[12, 12]}>
|
||||||
{links.map((link, index) => (
|
{links.map((link, index) => (
|
||||||
|
|
|
||||||
|
|
@ -227,22 +227,22 @@ const PersonPage: React.FC = () => {
|
||||||
<Row gutter={[16, 16]} style={{ padding: '24px 0' }}>
|
<Row gutter={[16, 16]} style={{ padding: '24px 0' }}>
|
||||||
<Col xs={12} md={6}>
|
<Col xs={12} md={6}>
|
||||||
<div className={styles.statCard}>
|
<div className={styles.statCard}>
|
||||||
<Statistic title="项目参与" value={138} valueStyle={{ color: '#1890ff' }} />
|
<Statistic title="项目参与" value={138} styles={{ content: { color: '#1890ff' } }} />
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={12} md={6}>
|
<Col xs={12} md={6}>
|
||||||
<div className={styles.statCard}>
|
<div className={styles.statCard}>
|
||||||
<Statistic title="代码提交" value={2300} suffix="+" valueStyle={{ color: '#52c41a' }} />
|
<Statistic title="代码提交" value={2300} suffix="+" styles={{ content: { color: '#52c41a' } }} />
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={12} md={6}>
|
<Col xs={12} md={6}>
|
||||||
<div className={styles.statCard}>
|
<div className={styles.statCard}>
|
||||||
<Statistic title="任务完成" value={95} suffix="%" valueStyle={{ color: '#722ed1' }} />
|
<Statistic title="任务完成" value={95} suffix="%" styles={{ content: { color: '#722ed1' } }} />
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={12} md={6}>
|
<Col xs={12} md={6}>
|
||||||
<div className={styles.statCard}>
|
<div className={styles.statCard}>
|
||||||
<Statistic title="获得勋章" value={12} valueStyle={{ color: '#faad14' }} />
|
<Statistic title="获得勋章" value={12} styles={{ content: { color: '#faad14' } }} />
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
@ -281,7 +281,7 @@ const PersonPage: React.FC = () => {
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
{/* 顶部个人信息卡片 */}
|
{/* 顶部个人信息卡片 */}
|
||||||
<Card className={styles.profileCard} bordered={false} bodyStyle={{ padding: 0 }}>
|
<Card className={styles.profileCard} variant="borderless" styles={{ body: { padding: 0 } }}>
|
||||||
<div className={styles.coverBg} />
|
<div className={styles.coverBg} />
|
||||||
<div className={styles.profileInfo}>
|
<div className={styles.profileInfo}>
|
||||||
<div className={styles.avatarWrapper}>
|
<div className={styles.avatarWrapper}>
|
||||||
|
|
@ -329,7 +329,7 @@ const PersonPage: React.FC = () => {
|
||||||
<Row gutter={24}>
|
<Row gutter={24}>
|
||||||
{/* 左侧信息栏 */}
|
{/* 左侧信息栏 */}
|
||||||
<Col xs={24} lg={8}>
|
<Col xs={24} lg={8}>
|
||||||
<Card className={styles.infoCard} bordered={false}>
|
<Card className={styles.infoCard} variant="borderless">
|
||||||
<div className={styles.infoTitle}>
|
<div className={styles.infoTitle}>
|
||||||
<UserOutlined style={{ color: '#1890ff' }} /> 基本信息
|
<UserOutlined style={{ color: '#1890ff' }} /> 基本信息
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -368,7 +368,7 @@ const PersonPage: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className={styles.infoCard} bordered={false}>
|
<Card className={styles.infoCard} variant="borderless">
|
||||||
<div className={styles.infoTitle}>
|
<div className={styles.infoTitle}>
|
||||||
<TrophyOutlined style={{ color: '#faad14' }} /> 技能特长
|
<TrophyOutlined style={{ color: '#faad14' }} /> 技能特长
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -383,7 +383,7 @@ const PersonPage: React.FC = () => {
|
||||||
|
|
||||||
{/* 右侧内容区 */}
|
{/* 右侧内容区 */}
|
||||||
<Col xs={24} lg={16}>
|
<Col xs={24} lg={16}>
|
||||||
<Card className={styles.infoCard} bordered={false}>
|
<Card className={styles.infoCard} variant="borderless">
|
||||||
<Tabs items={tabItems} />
|
<Tabs items={tabItems} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,10 @@ const AuthorityPage: React.FC = () => {
|
||||||
const fetchTableData = async () => {
|
const fetchTableData = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await getAuthorityList({ page: 1, pageSize: 1000 });
|
const res = await getAuthorityList();
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
setTableData(res.data?.list || []);
|
// GVA接口直接返回树形数组,不是分页格式
|
||||||
|
setTableData(res.data || []);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch authority list:', error);
|
console.error('Failed to fetch authority list:', error);
|
||||||
|
|
|
||||||
|
|
@ -84,9 +84,10 @@ const UserPage: React.FC = () => {
|
||||||
|
|
||||||
const fetchAuthorityOptions = async () => {
|
const fetchAuthorityOptions = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getAuthorityList({ page: 1, pageSize: 1000 });
|
const res = await getAuthorityList();
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const options = buildAuthorityOptions(res.data?.list || []);
|
// GVA接口直接返回树形数组,不是分页格式
|
||||||
|
const options = buildAuthorityOptions(res.data || []);
|
||||||
setAuthOptions(options);
|
setAuthOptions(options);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -4,42 +4,63 @@
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
|
|
||||||
export interface Authority {
|
export interface Authority {
|
||||||
authorityId: string;
|
authorityId: number | string;
|
||||||
authorityName: string;
|
authorityName: string;
|
||||||
parentId?: string;
|
parentId?: number | string;
|
||||||
defaultRouter?: string;
|
defaultRouter?: string;
|
||||||
children?: Authority[];
|
children?: Authority[];
|
||||||
dataAuthorityId?: Authority[];
|
dataAuthorityId?: Authority[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取角色列表 */
|
/** 获取角色列表 */
|
||||||
export const getAuthorityList = (data: { page: number; pageSize: number }) => {
|
export const getAuthorityList = (data?: { page?: number; pageSize?: number }) => {
|
||||||
return request.post('/authority/getAuthorityList', data);
|
return request.post('/authority/getAuthorityList', data || {});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 删除角色 */
|
/** 删除角色 */
|
||||||
export const deleteAuthority = (data: { authorityId: string }) => {
|
export const deleteAuthority = (data: { authorityId: number | string }) => {
|
||||||
return request.post('/authority/deleteAuthority', data);
|
return request.post('/authority/deleteAuthority', { authorityId: Number(data.authorityId) });
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 创建角色 */
|
/** 创建角色 */
|
||||||
export const createAuthority = (data: Authority) => {
|
export const createAuthority = (data: Authority) => {
|
||||||
return request.post('/authority/createAuthority', data);
|
return request.post('/authority/createAuthority', {
|
||||||
|
...data,
|
||||||
|
authorityId: Number(data.authorityId),
|
||||||
|
parentId: data.parentId ? Number(data.parentId) : 0,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 拷贝角色 */
|
/** 拷贝角色 */
|
||||||
export const copyAuthority = (data: { authority: Authority; oldAuthorityId: string }) => {
|
export const copyAuthority = (data: { authority: Authority; oldAuthorityId: number | string }) => {
|
||||||
return request.post('/authority/copyAuthority', data);
|
return request.post('/authority/copyAuthority', {
|
||||||
|
authority: {
|
||||||
|
...data.authority,
|
||||||
|
authorityId: Number(data.authority.authorityId),
|
||||||
|
parentId: data.authority.parentId ? Number(data.authority.parentId) : 0,
|
||||||
|
},
|
||||||
|
oldAuthorityId: Number(data.oldAuthorityId),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 设置角色资源权限 */
|
/** 设置角色资源权限 */
|
||||||
export const setDataAuthority = (data: { authorityId: string; dataAuthorityId: Authority[] }) => {
|
export const setDataAuthority = (data: { authorityId: number | string; dataAuthorityId: Authority[] }) => {
|
||||||
return request.post('/authority/setDataAuthority', data);
|
return request.post('/authority/setDataAuthority', {
|
||||||
|
authorityId: Number(data.authorityId),
|
||||||
|
dataAuthorityId: data.dataAuthorityId?.map(item => ({
|
||||||
|
...item,
|
||||||
|
authorityId: Number(item.authorityId),
|
||||||
|
})),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 修改角色 */
|
/** 修改角色 */
|
||||||
export const updateAuthority = (data: Authority) => {
|
export const updateAuthority = (data: Authority) => {
|
||||||
return request.put('/authority/updateAuthority', data);
|
return request.put('/authority/updateAuthority', {
|
||||||
|
...data,
|
||||||
|
authorityId: Number(data.authorityId),
|
||||||
|
parentId: data.parentId ? Number(data.parentId) : 0,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,19 @@ import request from '@/utils/request';
|
||||||
import type { ApiResponse } from '@/types/api';
|
import type { ApiResponse } from '@/types/api';
|
||||||
|
|
||||||
/** 获取角色按钮权限 */
|
/** 获取角色按钮权限 */
|
||||||
export const getAuthorityBtn = (data: { authorityId: string; menuID: number }) => {
|
export const getAuthorityBtn = (data: { authorityId: number | string; menuID: number }) => {
|
||||||
return request.post<ApiResponse<any>>('/authorityBtn/getAuthorityBtn', data);
|
return request.post<ApiResponse<any>>('/authorityBtn/getAuthorityBtn', {
|
||||||
|
...data,
|
||||||
|
authorityId: Number(data.authorityId),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 设置角色按钮权限 */
|
/** 设置角色按钮权限 */
|
||||||
export const setAuthorityBtn = (data: { authorityId: string; menuID: number; selected: string[] }) => {
|
export const setAuthorityBtn = (data: { authorityId: number | string; menuID: number; selected: string[] }) => {
|
||||||
return request.post<ApiResponse<any>>('/authorityBtn/setAuthorityBtn', data);
|
return request.post<ApiResponse<any>>('/authorityBtn/setAuthorityBtn', {
|
||||||
|
...data,
|
||||||
|
authorityId: Number(data.authorityId),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 检查是否可以移除按钮权限 */
|
/** 检查是否可以移除按钮权限 */
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,18 @@ export interface CasbinInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新角色API权限 */
|
/** 更新角色API权限 */
|
||||||
export const updateCasbin = (data: { authorityId: string; casbinInfos: CasbinInfo[] }) => {
|
export const updateCasbin = (data: { authorityId: number | string; casbinInfos: CasbinInfo[] }) => {
|
||||||
return request.post<ApiResponse<any>>('/casbin/updateCasbin', data);
|
return request.post<ApiResponse<any>>('/casbin/updateCasbin', {
|
||||||
|
...data,
|
||||||
|
authorityId: Number(data.authorityId),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 获取角色API权限列表 */
|
/** 获取角色API权限列表 */
|
||||||
export const getPolicyPathByAuthorityId = (data: { authorityId: string }) => {
|
export const getPolicyPathByAuthorityId = (data: { authorityId: number | string }) => {
|
||||||
return request.post<ApiResponse<{ paths: CasbinInfo[] }>>('/casbin/getPolicyPathByAuthorityId', data);
|
return request.post<ApiResponse<{ paths: CasbinInfo[] }>>('/casbin/getPolicyPathByAuthorityId', {
|
||||||
|
authorityId: Number(data.authorityId),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
||||||
|
|
@ -44,13 +44,18 @@ export const getBaseMenuTree = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 添加菜单权限关联 */
|
/** 添加菜单权限关联 */
|
||||||
export const addMenuAuthority = (data: { menus: number[]; authorityId: string }) => {
|
export const addMenuAuthority = (data: { menus: number[]; authorityId: number | string }) => {
|
||||||
return request.post('/menu/addMenuAuthority', data);
|
return request.post('/menu/addMenuAuthority', {
|
||||||
|
...data,
|
||||||
|
authorityId: Number(data.authorityId),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 获取菜单权限关联 */
|
/** 获取菜单权限关联 */
|
||||||
export const getMenuAuthority = (data: { authorityId: string }) => {
|
export const getMenuAuthority = (data: { authorityId: number | string }) => {
|
||||||
return request.post('/menu/getMenuAuthority', data);
|
return request.post('/menu/getMenuAuthority', {
|
||||||
|
authorityId: Number(data.authorityId),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 删除菜单 */
|
/** 删除菜单 */
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,11 @@ export const getUserList = (data: PageParams) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 设置用户权限 */
|
/** 设置用户权限 */
|
||||||
export const setUserAuthority = (data: { uuid: string; authorityId: string }) => {
|
export const setUserAuthority = (data: { uuid: string; authorityId: string | number }) => {
|
||||||
return request.post<ApiResponse<any>>('/user/setUserAuthority', data);
|
return request.post<ApiResponse<any>>('/user/setUserAuthority', {
|
||||||
|
...data,
|
||||||
|
authorityId: Number(data.authorityId),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 删除用户 */
|
/** 删除用户 */
|
||||||
|
|
@ -73,8 +76,11 @@ export const setSelfSetting = (data: any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 设置用户多角色权限 */
|
/** 设置用户多角色权限 */
|
||||||
export const setUserAuthorities = (data: { ID: number; authorityIds: string[] }) => {
|
export const setUserAuthorities = (data: { ID: number; authorityIds: (number | string)[] }) => {
|
||||||
return request.post<ApiResponse<any>>('/user/setUserAuthorities', data);
|
return request.post<ApiResponse<any>>('/user/setUserAuthorities', {
|
||||||
|
...data,
|
||||||
|
authorityIds: data.authorityIds.map(id => Number(id)),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 获取用户信息 */
|
/** 获取用户信息 */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue