feat: 系统管理

This commit is contained in:
2025-10-30 16:12:02 +08:00
parent 1e5ea1879b
commit 126ee7b50a
11 changed files with 351 additions and 49 deletions

View File

@@ -0,0 +1,187 @@
import * as IconPark from '@icon-park/react';
import { Input, List, Modal, Pagination } from 'antd';
import { useState } from 'react';
const allIcons = Object.keys(IconPark);
// 动态构造 iconMap
const iconMap: Record<string, React.ReactNode> = {};
allIcons.forEach((iconName) => {
// 排除不需要的属性(如默认导出等)
if (
iconName !== 'default' &&
typeof IconPark[iconName as keyof typeof IconPark] === 'function'
) {
const IconComponent = IconPark[
iconName as keyof typeof IconPark
] as React.ComponentType<any>;
iconMap[iconName] = <IconComponent theme="outline" size="16" />;
}
});
// 图标选项列表
const iconOptions = Object.keys(iconMap).map((key) => ({
label: (
<span>
{iconMap[key]} {key}
</span>
),
value: key,
}));
interface IconSelectorProps {
value?: string;
onChange?: (value: string) => void;
}
const IconSelector: React.FC<IconSelectorProps> = ({ value, onChange }) => {
const [open, setOpen] = useState(false);
const [searchValue, setSearchValue] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 48;
// 过滤图标
const filteredIcons = iconOptions.filter(
(option) =>
option.value.toLowerCase().includes(searchValue.toLowerCase()) ||
option.value.toLowerCase().includes(searchValue.toLowerCase()),
);
// 分页数据
const paginatedIcons = filteredIcons.slice(
(currentPage - 1) * pageSize,
currentPage * pageSize,
);
const handleSelect = (iconName: string) => {
onChange?.(iconName);
setOpen(false);
setCurrentPage(1);
setSearchValue('');
};
const handleClear = () => {
onChange?.('');
setOpen(false);
};
return (
<>
<div
onClick={() => setOpen(true)}
style={{
border: '1px solid #d9d9d9',
borderRadius: 6,
padding: '4px 11px',
cursor: 'pointer',
minHeight: 32,
display: 'flex',
alignItems: 'center',
}}
>
{value ? (
<span>
{iconMap[value as keyof typeof iconMap]}
<span style={{ marginLeft: 8 }}>{value}</span>
</span>
) : (
<span style={{ color: '#bfbfbf' }}></span>
)}
</div>
<Modal
title="选择图标"
open={open}
onCancel={() => {
setOpen(false);
setCurrentPage(1);
setSearchValue('');
}}
onOk={() => setOpen(false)}
width={800}
styles={{ body: { padding: 0 } }}
okText="确定"
cancelText="取消"
>
<div style={{ padding: 16 }}>
<Input.Search
placeholder="搜索图标..."
value={searchValue}
onChange={(e) => {
setSearchValue(e.target.value);
setCurrentPage(1);
}}
style={{ marginBottom: 16 }}
/>
{filteredIcons.length === 0 ? (
<IconPark.Empty title="未找到相关图标" />
) : (
<>
<List
grid={{ gutter: 16, column: 8 }}
dataSource={paginatedIcons}
renderItem={(item) => (
<List.Item
onClick={() => handleSelect(item.value)}
style={{
cursor: 'pointer',
textAlign: 'center',
border:
value === item.value
? '1px solid #1890ff'
: '1px solid transparent',
borderRadius: 6,
padding: 8,
}}
>
<div>
<div style={{ fontSize: 20, marginBottom: 4 }}>
{iconMap[item.value as keyof typeof iconMap]}
</div>
<div
style={{
fontSize: 12,
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{item.value}
</div>
</div>
</List.Item>
)}
/>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
marginTop: 16,
}}
>
<Pagination
current={currentPage}
pageSize={pageSize}
total={filteredIcons.length}
onChange={setCurrentPage}
showSizeChanger={false}
size="small"
/>
<div>
<a onClick={handleClear} style={{ marginRight: 16 }}>
</a>
</div>
</div>
</>
)}
</div>
</Modal>
</>
);
};
export default IconSelector;