From ae75ea4e2cd740b6b99375ec45e27ca14fb93494 Mon Sep 17 00:00:00 2001 From: chenhao Date: Fri, 3 Jul 2026 15:52:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(permiss=E6=B7=BB=E5=8A=A0icons):=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=A1=A8=E6=A0=BC=E8=A1=8C=E6=8B=96=E6=8B=BD?= =?UTF-8?q?=E6=8E=92=E5=BA=8F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/locales/zh-CN.json | 4 +- .../src/pages/access/permissions/index.tsx | 139 +++++++++++++----- 2 files changed, 104 insertions(+), 39 deletions(-) diff --git a/frontend/src/locales/zh-CN.json b/frontend/src/locales/zh-CN.json index 6e51cfd..aa2cf32 100644 --- a/frontend/src/locales/zh-CN.json +++ b/frontend/src/locales/zh-CN.json @@ -478,7 +478,9 @@ "iconEmpty": "未找到匹配的图标", "showIconLibrary": "显示图标库", "hideIconLibrary": "收起图标库", - "iconLoadingMore": "已加载 {{current}} / {{total}} 个图标" + "iconLoadingMore": "已加载 {{current}} / {{total}} 个图标", + "commonIcons": "常用图标", + "allIcons": "全部图标" }, "theme": { "settings": "主题设置", diff --git a/frontend/src/pages/access/permissions/index.tsx b/frontend/src/pages/access/permissions/index.tsx index 0f430ab..ac01859 100644 --- a/frontend/src/pages/access/permissions/index.tsx +++ b/frontend/src/pages/access/permissions/index.tsx @@ -4,6 +4,7 @@ import { Button, Col, Drawer, Form, Input, InputNumber, Popconfirm, Row, Select, import { DndContext, PointerSensor, useSensor, useSensors, DragEndEvent } from '@dnd-kit/core'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; +import * as AntdIcons from "@ant-design/icons"; import { CSS } from '@dnd-kit/utilities'; import { ApartmentOutlined, @@ -39,6 +40,7 @@ import "./index.less"; const { Text } = Typography; type TreePermission = SysPermission & { key: number; children?: TreePermission[] }; +type MenuIconComponent = React.ElementType<{ "aria-hidden"?: string | boolean }>; const legacyIconAliases: Record = { dashboard: "DashboardOutlined", @@ -53,11 +55,30 @@ const legacyIconAliases: Record = { setting: "SettingOutlined" }; -const menuIconComponents: Record> = { +const commonMenuIconNames = [ + "DashboardOutlined", + "VideoCameraOutlined", + "UserOutlined", + "TeamOutlined", + "SafetyCertificateOutlined", + "SettingOutlined", + "DesktopOutlined", + "ShopOutlined", + "ApartmentOutlined", + "BookOutlined", + "FolderOutlined", + "MenuOutlined", + "ClusterOutlined" +]; + +const fallbackMenuIconComponents: Record = { ApartmentOutlined, BookOutlined, + ClusterOutlined, DashboardOutlined, DesktopOutlined, + FolderOutlined, + MenuOutlined, SafetyCertificateOutlined, SettingOutlined, ShopOutlined, @@ -66,6 +87,13 @@ const menuIconComponents: Record> = { VideoCameraOutlined }; +const menuIconComponents = Object.entries(AntdIcons).reduce>((icons, [name, component]) => { + if (name.endsWith("Outlined") && (typeof component === "function" || (typeof component === "object" && component !== null))) { + icons[name] = component as MenuIconComponent; + } + return icons; +}, {...fallbackMenuIconComponents}); + interface RowProps extends React.HTMLAttributes { 'data-row-key': string; } @@ -114,6 +142,7 @@ const DragHandle = () => { ); }; +const commonMenuIconOptions = commonMenuIconNames.filter((iconName) => menuIconComponents[iconName]); const menuIconOptions = Object.keys(menuIconComponents).sort((left, right) => left.localeCompare(right)); function renderSelectableIcon(iconName?: string) { @@ -223,10 +252,18 @@ export default function Permissions() { .map((permission) => ({ value: permission.permId, label: permission.name })); }, [data, currentPermType]); + const filteredCommonIconOptions = useMemo(() => { + const keyword = iconSearchKeyword.trim().toLowerCase(); + if (!keyword) return commonMenuIconOptions; + return commonMenuIconOptions.filter((iconName) => iconName.toLowerCase().includes(keyword)); + }, [iconSearchKeyword]); + const filteredIconOptions = useMemo(() => { const keyword = iconSearchKeyword.trim().toLowerCase(); - if (!keyword) return menuIconOptions; - return menuIconOptions.filter((iconName) => iconName.toLowerCase().includes(keyword)); + return menuIconOptions.filter((iconName) => { + const hitKeyword = keyword ? iconName.toLowerCase().includes(keyword) : true; + return hitKeyword && !commonMenuIconOptions.includes(iconName); + }); }, [iconSearchKeyword]); const visibleIconOptions = useMemo(() => filteredIconOptions.slice(0, visibleIconCount), [filteredIconOptions, visibleIconCount]); @@ -254,6 +291,41 @@ export default function Permissions() { setIconSearchKeyword(""); }; + const renderIconPickerButton = (iconName: string) => { + const active = selectedIcon === iconName; + return ( + + + + ); + }; + const onDragEnd = async ({ active, over }: DragEndEvent) => { if (active.id !== over?.id) { const activeId = Number(active.id); @@ -606,43 +678,34 @@ export default function Permissions() { setVisibleIconCount((count) => Math.min(count + 120, filteredIconOptions.length)); } }} style={{ maxHeight: 240, overflowY: "auto", border: "1px solid #f0f0f0", borderRadius: 8, padding: 12, background: "#fafafa" }}> + {filteredCommonIconOptions.length ? <> + {t("permissionsExt.commonIcons")} +
+ {filteredCommonIconOptions.map(renderIconPickerButton)} +
+ : null} + {visibleIconOptions.length ? {t("permissionsExt.allIcons")} : null}
- {visibleIconOptions.map((iconName) => { - const active = selectedIcon === iconName; - return ( - - - - ); - })} + {visibleIconOptions.map(renderIconPickerButton)}
- {!filteredIconOptions.length ?
{t("permissionsExt.iconEmpty")}
: null} + {!filteredCommonIconOptions.length && !filteredIconOptions.length ?
{t("permissionsExt.iconEmpty")}
: null} {visibleIconOptions.length < filteredIconOptions.length ?
{t("permissionsExt.iconLoadingMore", { current: visibleIconOptions.length, total: filteredIconOptions.length })}
: null} : null}